Skip to content

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.

bash
vericue-inject -- ./your-qt-app
# VERICUE_ENDPOINT=/run/user/1000/vericue/vericue-4213.sock

The injected server announces the local endpoint it listens on. In another terminal, drive it with any client - for example the CLI:

bash
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:

bash
vericue-inject --token s3cret -- ./your-qt-app --your-flag foo

Supported configurations

SupportedNot supported
PlatformLinux x64Windows has its own launch path - see the Windows guide; macOS has none, embed instead
Architecturex86-64 - the only architecture veriCue ships and tests a Linux package foranything else, including Linux arm64: there is no package to load into such a process
Qt linkagedynamic (libQt5Core.so / libQt6Core.so)statically linked Qt - nothing can be preloaded into it
Qt version5.15 and 6.x, matching the package variant you downloada Qt older than the package's Qt (Qt is forward compatible only)
ToolkitsQt Widgets and Qt Quick/QML, including mixed-
Binarythe application executable itself; stripped release builds are finewrapper/launcher scripts, and set-user-ID / set-group-ID binaries (the loader drops LD_PRELOAD)
Processprocesses that construct a QCoreApplication/QGuiApplication/QApplicationprocesses that only dlopen Qt later
Qt modulesyour deployment provides Qt Core, Gui and Network - see what Qt your application needsa 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:

bash
vericue-inject --check -- ./your-qt-app
vericue-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:

bash
vericue-inject -- ./your-qt-app | grep -m1 VERICUE_ENDPOINT
# VERICUE_ENDPOINT=/run/user/1000/vericue/vericue-4213.sock

Pin the path yourself with --endpoint when a harness needs to know it up front:

bash
vericue-inject --endpoint /run/user/1000/my-app.sock -- ./your-qt-app

Endpoint 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_method on 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:

bash
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>:

bash
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:

FlagEmbedded equivalentEffect
--token TsetAuthToken("T")clients must present T in the handshake
--license FILEsetLicenseFile("FILE")node-locked RSA-signed key
--license-server HOST:PORTsetLicenseServer(...)floating license lease
bash
vericue-inject --token s3cret --license /etc/vericue/license.json -- ./your-qt-app

With 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, ping and version and refuses everything else with trial_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/embedding

What is checked, and what to do about each:

RefusalMeaningFix
statically linked Qtthere is no libQtNCore in the process to piggyback onbuild against a shared Qt, or embed
architecture mismatchthe probe would be loaded into a process of a different architectureveriCue ships Linux packages for x86-64 only - embed on any other architecture
the target is a scripta 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_PATHpoint the launcher at the real executable, or pass --no-preflight to run it unchecked
Qt major mismatchQt 5 and Qt 6 are not binary compatibledownload the other package variant
Qt minor mismatchthe probe is built against a newer Qt than the application runsuse a package built against the application's Qt or older
set-user-ID / set-group-ID targetthe loader drops LD_PRELOAD for AT_SECURE binariesrun a non-setuid build, or embed
does not link a dynamic Qtnot a Qt binary, or a wrapper/helperpoint the launcher at the real application binary
probe not foundthe package layout was broken upkeep bin/ and lib/ together, or set VERICUE_INJECT_LIB
toolkit plugin missinglibvericue-widgets.so / libvericue-quick.so is not beside the server libraryre-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-preflight skips every check above, and the LD_LIBRARY_PATH fix that depends on them. Only useful when you know the target is fine and the check is wrong; --check --no-preflight reports preflight SKIPPED rather 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. version reports 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:

bash
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 env

Still 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.

Released under a commercial licence. Privacy · Terms