Zero-source-change runs on Linux (vericue-inject)
This page is the Linux mechanism, not the command to learn
The command is vericue run - one front door, the same flags on Linux and Windows. This page documents the Linux backend it uses, and is what you want when you are diagnosing a refusal or driving the injector directly. Windows has a different mechanism with its own envelope: the Windows guide.
vericue-inject drives an unmodified Qt application with veriCue - no code changes, no rebuild, no linking. On the configurations listed below it is a supported path, not a demo: the same server, the same protocol, the same authentication and the same licensing you get from embedding.
It is the fastest way to start, and for many teams it is where they stay. Embedding remains the recommended setup where injection cannot reach - static Qt, hardened processes, macOS, architectures other than x86-64 - and where you want the server compiled out of release builds entirely.
Start with vericue run (from 0.5.0)
vericue run ./your-qt-app is the front door and the command to reach for first: one stable command, the same flags on every platform, and the mechanism chosen for you. On Linux it starts your application throughvericue-inject, so everything on this page - the compatibility envelope, the preflight and every refusal message - applies to it unchanged. On Windows the mechanism is a native launcher that loads the Runtime with a launch-time DLL injection; the command is the same, but the compatibility envelope and the antivirus/EDR implications are their own page, Windows launch path.
This page is the mechanism itself. Read it when you want to see what is being decided and why, when you are diagnosing a refusal, or when the machine that starts your application has no Python on it.
60-second run
Install a Linux package (see Installation); it puts bin/vericue-inject next to lib/libvericue-inject.so and the rest of the runtime.
vericue-inject -- ./your-qt-app
# VERICUE_ENDPOINT=/run/user/1000/vericue/vericue-4213.sockThe injected server announces the local endpoint it listens on. In another terminal, drive it with any client - for example the CLI:
python -m vericue --endpoint /run/user/1000/vericue/vericue-4213.sock list_objects
python -m vericue --endpoint /run/user/1000/vericue/vericue-4213.sock find_object --path "MainWindow/okButton"The server is now running inside your application, exposing its live object tree over the veriCue protocol - the full command surface, not read-only introspection.
Everything after -- is your target command and its own arguments:
vericue-inject --token s3cret -- ./your-qt-app --your-flag fooSupported configurations
| Supported | Not supported | |
|---|---|---|
| Platform | Linux x64 | Windows has its own launch path - see the Windows guide; macOS has none, embed instead |
| Architecture | x86-64 - the only architecture veriCue ships and tests a Linux package for | anything else, including Linux arm64: there is no package to load into such a process |
| Qt linkage | dynamic (libQt5Core.so / libQt6Core.so) | statically linked Qt - nothing can be preloaded into it |
| Qt version | 5.15 and 6.x, matching the package variant you download | a Qt older than the package's Qt (Qt is forward compatible only) |
| Toolkits | Qt Widgets and Qt Quick/QML, including mixed | - |
| Binary | the application executable itself; stripped release builds are fine | wrapper/launcher scripts, and set-user-ID / set-group-ID binaries (the loader drops LD_PRELOAD) |
| Process | processes that construct a QCoreApplication/QGuiApplication/QApplication | processes that only dlopen Qt later |
| Qt modules | your deployment provides Qt Core, Gui and Network - see what Qt your application needs | a deployment without QtNetwork: the Runtime links it for its own transport |
Everything in the right-hand column is detected before launch and refused with a diagnosis - see When preflight refuses.
The feature set is not reduced. Interaction, screenshots, model/view access, subscriptions, recording and performance commands all work exactly as they do in an embedded server; the injected runtime resolves the Widgets and Quick backends at runtime from the modules your application already loaded - which is why a Widgets application never gains a Qt Quick dependency from veriCue, and a QML application never gains QtWidgets.
The Runtime uses your Qt throughout. It carries none of its own and must not be given any: copying veriCue's Qt libraries into your deployment puts two QtCore builds in one process, which Qt refuses at startup.
Check before you run
--check runs the whole preflight and reports what it found, without starting anything:
vericue-inject --check -- ./your-qt-appvericue-inject: preflight OK
target ./your-qt-app (ELF64 x86-64)
target Qt 6.7.1 [/opt/qt/6.7.1/lib/libQt6Core.so.6]
toolkits quick
probe /opt/vericue/lib/libvericue-inject.so
probe build veriCue 0.3.5, Qt 6.7.1
runtime /opt/vericue/lib
plugins libvericue-widgets.so libvericue-quick.so
transport local IPC (auto, announced as VERICUE_ENDPOINT)This is the first thing to attach to a support request.
Transport: local IPC by default
An injected run uses the local IPC transport: a user-private UNIX socket that no other machine - and no other user - can reach. Nothing is exposed on the network unless you ask for it.
A script can capture the endpoint from stdout:
vericue-inject -- ./your-qt-app | grep -m1 VERICUE_ENDPOINT
# VERICUE_ENDPOINT=/run/user/1000/vericue/vericue-4213.sockPin the path yourself with --endpoint when a harness needs to know it up front:
vericue-inject --endpoint /run/user/1000/my-app.sock -- ./your-qt-appEndpoint lifetime and cleanup
- The default endpoint lives in
$XDG_RUNTIME_DIR/vericue/and carries the application's PID, so concurrent runs never collide. - On a normal exit the server removes its own endpoint: it is parented to the application object, so the application's own shutdown tears it down.
- If the application is killed (
SIGKILL, a crash), the socket file survives. That is harmless: the next server to want that path probes it, finds nobody listening, and takes it over. Nothing needs to be cleaned up by hand. - To end a run from the harness, prefer closing the application through veriCue itself (
invoke_methodon the window, or whatever your app's quit path is) - the endpoint is then removed as part of the normal exit.
Using TCP instead
Pass --port to opt into TCP - the right choice when the client runs on a different host or outside the app's container:
vericue-inject --port 4242 -- ./your-qt-app
python -m vericue --port 4242 list_objects--port 0 (or VERICUE_PORT=0) lets the OS pick a free port; the injected server prints it as VERICUE_PORT=<n>:
vericue-inject --port 0 -- ./your-qt-app | grep -m1 VERICUE_PORT
# VERICUE_PORT=45123--port and --endpoint are mutually exclusive.
Authentication and licensing
An injected server is subject to exactly the rules an embedded one is - this is enforced by the same code and covered by the same tests. The launcher just passes your configuration through:
| Flag | Embedded equivalent | Effect |
|---|---|---|
--token T | setAuthToken("T") | clients must present T in the handshake |
--license FILE | setLicenseFile("FILE") | node-locked RSA-signed key |
--license-server HOST:PORT | setLicenseServer(...) | floating license lease |
vericue-inject --token s3cret --license /etc/vericue/license.json -- ./your-qt-appWith none of them the run is a trial, identically to an embedded server that was never given a license. In particular:
- The licensed concurrent automation session budget is one budget. Injection does not widen it, and it is shared across both transports.
- An expired trial answers
handshake,pingandversionand refuses everything else withtrial_expired(1012). - An expired paid key refuses to start the server at all. The probe reports the reason on stderr and your application keeps running normally - injection never takes the host process down with it.
A licensing configuration you asked for and that cannot be applied - a --license-server that is not HOST:PORT, a --license file that cannot be read - refuses the run instead of falling back to trial mode. Both the launcher and the probe enforce this. A trial nobody chose looks exactly like the license you thought you were running on, right up to the point where a second concurrent session is refused in CI.
When preflight refuses
The launcher checks the target before it launches anything, so a mismatch fails with an explanation instead of a loader error deep inside your application. Every refusal names the cause, a corrective action, and the fallback.
vericue-inject: the target is a statically linked binary with Qt built in
(Qt 6.7.1 (x86_64-little_endian-lp64 static (dynamic) release build)
What to do:
- a static binary has no dynamic loader step, so nothing can be preloaded into it
- veriCue cannot inject into static Qt - this is not a packaging problem and no package fixes it
- build the application against a shared Qt, or embed the veriCue server
Fallback: embed the veriCue server in the application. It supports every
configuration injection does not, and is the recommended setup for CI.
https://vericue.dev/docs/guides/embeddingWhat is checked, and what to do about each:
| Refusal | Meaning | Fix |
|---|---|---|
| statically linked Qt | there is no libQtNCore in the process to piggyback on | build against a shared Qt, or embed |
| architecture mismatch | the probe would be loaded into a process of a different architecture | veriCue ships Linux packages for x86-64 only - embed on any other architecture |
| the target is a script | a wrapper hides the architecture, Qt version and toolkits of the binary it runs, and the launcher cannot put that binary's Qt on LD_LIBRARY_PATH | point the launcher at the real executable, or pass --no-preflight to run it unchecked |
| Qt major mismatch | Qt 5 and Qt 6 are not binary compatible | download the other package variant |
| Qt minor mismatch | the probe is built against a newer Qt than the application runs | use a package built against the application's Qt or older |
| set-user-ID / set-group-ID target | the loader drops LD_PRELOAD for AT_SECURE binaries | run a non-setuid build, or embed |
| does not link a dynamic Qt | not a Qt binary, or a wrapper/helper | point the launcher at the real application binary |
| probe not found | the package layout was broken up | keep bin/ and lib/ together, or set VERICUE_INJECT_LIB |
| toolkit plugin missing | libvericue-widgets.so / libvericue-quick.so is not beside the server library | re-extract the package without moving files out of lib/ |
Wrapper scripts
A launcher script is not a supported target. LD_PRELOAD would be inherited by the binary the script eventually execs, but nothing about that binary can be checked from the script: not its architecture, not its Qt version, not its toolkits. The launcher also derives the target's own Qt directory from it and appends that to LD_LIBRARY_PATH - so a script wrapping an application that ships its own Qt is the case where the probe resolves the system Qt instead and Qt aborts the process with "Cannot mix incompatible Qt library". That is the failure this preflight exists to prevent, so the script is refused and the message names the binary it appears to run.
Point vericue-inject at the real executable, and let the script's own environment setup happen around it - or, if you know what you are doing, run the script anyway with the one escape hatch:
--no-preflightskips every check above, and theLD_LIBRARY_PATHfix that depends on them. Only useful when you know the target is fine and the check is wrong;--check --no-preflightreportspreflight SKIPPEDrather than claiming a verdict it did not reach. Support will ask why it was needed.
How it works
vericue-inject sets LD_PRELOAD to load libvericue-inject.so into your application before it starts. That library registers a Q_COREAPP_STARTUP_FUNCTION, which Qt invokes the moment your app constructs its QApplication (or QGuiApplication / QCoreApplication). At that point the probe starts a VeriCueServer parented to the application object.
- No binary patching, no threads, no polling - it hooks Qt's own startup callback.
- Inert in non-Qt processes - if the process never constructs a
QCoreApplication, the startup function simply never runs. A helper process your application spawns is therefore unaffected unless it is a Qt process itself. - Never aborts the host - if the server can't start (a taken port, an expired key, a busy endpoint) the probe prints a warning to stderr and the application keeps running normally.
- Only the toolkit you already use - Widgets and Quick support live in plugins the runtime loads on demand, so injecting into a QML application does not drag QtWidgets into it, and vice versa.
versionreports what was resolved.
The launcher also appends the target's own Qt directory to LD_LIBRARY_PATH, so the probe resolves the same Qt build the application uses. Without that, a machine with a distribution Qt installed alongside a vendored one can end up with two Qt copies in one process, which Qt aborts on.
Deployment and CI
The launcher and the runtime are one deliverable. bin/vericue-inject finds lib/libvericue-inject.so relative to itself, that probe finds libvericue-server.so beside it, and the server loads libvericue-widgets.so / libvericue-quick.so from that same directory. Keep bin/ and lib/ together and there is nothing to configure. If you have to split them, point VERICUE_INJECT_LIB at the probe.
In a build tree, VERICUE_INJECT_LIB=<build>/server/inject/libvericue-inject.so (or VERICUE_BUILD_DIR=<build>) is the explicit form; without it the launcher searches the install layout, its own directory and any sibling build* tree, and prefers the probe whose Qt major matches the target.
A CI job looks like this:
set -euo pipefail
vericue-inject --check -- ./your-qt-app # fail fast on a bad runner
QT_QPA_PLATFORM=offscreen vericue-inject --token "$VERICUE_TOKEN" \
-- ./your-qt-app > app.log 2>&1 &
for _ in $(seq 1 60); do
ENDPOINT="$(grep -m1 -oE 'VERICUE_ENDPOINT=\S+' app.log | cut -d= -f2- || true)"
[ -n "$ENDPOINT" ] && break
sleep 0.5
done
[ -n "$ENDPOINT" ] || { cat app.log; exit 1; }
python -m vericue --endpoint "$ENDPOINT" --token "$VERICUE_TOKEN" ping
VERICUE_ENDPOINT="$ENDPOINT" pytest tests/ # your own tests read it from the envStill embed when it fits better
Embedding gives you a server that starts on your terms, can be compiled out of release builds, and works on every platform veriCue supports. Injection gives you a run today against the binary you already have. Both are supported; pick per situation, not once. See Embedding the server.
Security
The injected server is the same server you would embed, and a connected client has full control of the UI: it can read widget contents, click anything, invoke methods and take screenshots.
By default that surface is local only. The endpoint is a UNIX socket created with owner-only permissions in your runtime directory, so it is not reachable from another host by construction and not openable by another user on the same machine.
The moment you pass --port you switch to plaintext TCP on all interfaces, which is unauthenticated unless you also pass --token. Anyone who can reach that port can drive your application. Only use TCP on machines and networks you trust, always pass --token outside a single-developer box, and firewall or tunnel the port rather than exposing it. See the security model in the embedding guide and the transport comparison for the full picture.

