Troubleshooting
Headings are the message you saw. Search for yours.
Read the request trace first. It shows every request the sandbox received, with both sides’ bodies and headers.
Setup and credentials
veris doctor is one line per check. It always prints the binary it is running, your
login, the control plane, docker, cloudflared, the certificate, then this folder’s
project and its sandbox. The rest appear when they apply: gateway mode and the vendor
hostnames the plane serves need a login and a plane that answers; the environment line
comes once a project file is found or anything names an environment; a VERIS_API_KEY
in your shell that outranks the profile’s own key gets a line of its own; and a live
sandbox adds a line per twin, its clock, and its callback registration. ✓ passed,
! worth knowing, ✗ will fail a run, and a → Next: line under a check names the
command that answers it.
The /veris:setup or $veris:setup command does not exist
The plugin’s commands load when a session starts, so a session that was already
open does not have them. Restart your agent and ask again. If the command is
still missing, check the install: claude plugin list in Claude Code,
codex plugin list in Codex.
veris: command not found
The installer writes the binary to ~/.local/bin unless VERIS_INSTALL_DIR says
otherwise, and prints a note when that directory is not on your PATH. Add it to
your shell profile, then check with:
veris versionNot logged in for profile 'default' (no API key)
veris loginIt prints a pairing code and a console URL. Someone signed in approves the code for
one of their organisations, and the key that comes back is saved under a profile in
~/.veris/twin.yaml. In CI, where nobody is there to approve a pairing, pipe an
existing key in instead:
printf '%s' "$KEY" | veris login --key-stdinveris whoami then says which key, organisation and control plane every command
would use.
VERIS_API_KEY from your shell ... is sent to ... instead of profile '<name>'s own key
A key in your environment outranks the profile you logged in with, and unless the
shell names the plane as well it is sent to the profile’s plane, where a key minted
for another one is refused. Either unset VERIS_API_KEY to use the profile, or
export VERIS_API_BASE for the plane that key belongs to.
dial tcp: lookup svc.api.veris.ai: no such host
Nothing here can reach the control plane. If your own shell reaches it and only your
coding agent does not — veris doctor in the agent showing this beside
docker on PATH but 'docker info' failed: permission denied ... docker.sock — the
agent is running in a sandbox with no network and no Docker socket. Nothing in the
project works around that. Restart the agent with network and Docker access.
411 POST requests require a Content-length header
Send a body on every POST, even where the route takes no parameters.
curl -X POST ... -H 'Content-Type: application/json' -d '{}'Runs
The receipt is empty and the run failed
veris: the sandbox received nothing from this run.A run that deployed its own sandbox and sent it nothing fails on that alone, whatever
the tests said, and so does any run with a service requirement in play —
--require-service on the command line, or require_service in the environment’s
entry in .veris/twin.yaml, which setup records. A run attached to this folder’s
sandbox with no requirement prints the warning without failing. Causes, in
diagnostic order:
- Your suite never called the dependency. Mocks still active, tests filtered out, wrong path.
- The traffic went to the real vendor, because that host has no twin in your
environment.
veris serviceslists the catalog andveris env getthe environment;--strictturns the leak into a421instead of letting it out. - TLS trust failed inside your container, so no request completed. See below.
- Nothing was intercepted for that twin at all, and its URL was handed to your command. See below.
- The vendor call came from a process the run did not start. See below.
A receipt reporting only /veris/* control-plane requests is the same finding: that
traffic is your own reads of the sandbox, and it never proves the code under test
called anything.
the run did not call a service it was required to call (exit 3)
A service requirement failed: --require-service on the command line, or
require_service in the environment’s entry in .veris/twin.yaml. Same causes as an
empty receipt. Drop the requirement temporarily and read the receipt to see what did
arrive.
veris: <service>: not proxied; handed <VAR>=<url>
The control plane serves no vendor hostname for that twin, so its traffic is not
intercepted; its URL goes to your command under the variable it already reads. For a
data plane — a database, a screening service — that is the design. For an ordinary
vendor twin it is not, and veris doctor’s vendor-hostnames line names the twins in
that state. --route <service>=<host> supplies a hostname for one run.
A variable you set yourself with -e is never overwritten, and the run says so:
veris: <service>: not proxied, and not handed over: $VAR was set with -e.
certificate verify failed: unable to get local issuer certificate
An SDK that ships its own certificate bundle. The flag patches the CA files inside a container image, so the run has to be containerised:
veris run --image <your test image> --patch-bundled-cas -- <your test command>See Certificates and SDK trust.
APIConnectionError from a Stripe SDK
The same thing, wrapped. stripe-python and stripe-ruby hide certificate
failures behind a generic connection error. Use --patch-bundled-cas.
N TLS handshake(s) rejected ... 0 requests completed
The proxy telling you directly that the client refused its certificate. The line ends
with a Next: step computed from what the run already tried; follow that one. Either
a bundled bundle, in which case --patch-bundled-cas, or certificate pinning, in
which case there is no workaround. See
Certificates.
N TLS handshake(s) ended ... 0 requests completed
The same shape without a TLS alert to confirm it: the connection closed rather than refusing, which is what Node does. Read it as a refusal when the workload’s own error looks like a connection or certificate failure. It fails the run only when the receipt is empty as well.
My SDK reports a connection error but the receipt shows traffic
Something else on that host completed a request, which suppresses the trust diagnostic. Read the paths in the trace to see whose traffic it actually was.
Vendor calls fail in a container the run did not start
veris run --image hands the trust environment to the container it starts and to
nothing else. A compose service that joins the proxy’s network namespace shares the
redirect without the trust, so every vendor call from it dies —
SELF_SIGNED_CERT_IN_CHAIN in Node — while the workload looks healthy and the
receipt shows nothing for that twin.
The proxy container is named veris-proxy-<pid> and writes veris.env and
veris-ca.pem into the directory it mounts at /veris-share. Give the sibling that
file as an env_file and the same directory as a volume. The share is minted per
run, so read the host path rather than hardcoding it:
docker inspect -f '{{range .Mounts}}{{if eq .Destination "/veris-share"}}{{.Source}}{{end}}{{end}}' <veris-proxy-container>--keep-proxy leaves the container and its share in place afterwards.
! ledgers differ (engine N, sandbox M)
Two counts of the same run: what the proxy saw leave your app, and what the sandbox recorded since the run’s watermark. A gap means requests died between the two, or something outside this run was also using the sandbox. Read the trace.
The run exited 4
The outcome is indeterminate: the command itself came back clean, but an assertion
could not be judged, because the sandbox’s own request log was not readable or was
read only to its row cap. A --fresh sandbox is kept when that happens, since its
world is the evidence, and the run says so. veris down deletes it.
The image must not run as uid 14741
The proxy runs as uid 14741, and your image cannot share it. Change your image’s
user, or move the proxy with --proxy-uid.
My container cannot publish its own ports
Your container shares a network with the proxy. Use --expose instead. Ports
8081 and 8443 belong to the proxy.
Twins
A 404 or 400 I did not expect
Inconclusive on its own, because it is exactly what the real vendor sends. Check your credential, the API version, the payload shape, and whether the row you are addressing exists.
To find out whether an operation is served at all, do not guess from the status. Ask the control plane.
path '<path>' does not match a published operation of this service
That operation is not part of this twin’s surface. Also check your API version, which can move a path.
<METHOD> is not published for path '<path>'
The path is served, that method is not.
A refusal that names itself unsupported, usually 501
Conclusive. That operation is not modelled. Design around what is, and tell us if you need it.
A bare 500
A defect. Capture the request and the trace entry and report it.
Lots of 502
Check whether the sandbox is still alive and inside its TTL: veris status.
My wrong-credential test passes
Any well-formed credential authenticates by default. Set auth.mode to
enforced, or send no credential header at all. See
Credentials.
My negative auth test passes with an obviously broken key
In permissive mode any non-empty value is valid, including an unresolved
${API_KEY} template your harness leaked into the header. Send no header.
An armed fault never reached my code
Your SDK probably retried it internally and absorbed it. Most vendor clients retry 5xx and connection errors. Arm a 429 instead. See Faults.
A request hung, or timed out where it should not
Check for a fault left armed by an earlier probe:
veris sandbox data get <twin> faults
veris sandbox data delete <twin> faults id=<fault id> --yesA twin that sends nothing at all shows a status of — in the trace, which is a hang
fault. Otherwise check the client’s own per-request timeout: an error path can be far
slower than a success path.
The trace looks like my app sent nothing
Filter it. veris sandbox trace --tier handler is the traffic your application sent;
--tier control is your own seeding and read-back, which after a heavy seed can
drown everything else, and --tier fault is what an armed fault produced.
Sandboxes
veris sandbox reset is refused with 409
The sandbox booted from a snapshot or a promoted baseline, and reseeding would
throw away the world it was told to start from. A fresh copy of that world is
veris down && veris up.
A sandbox stayed provisioning for minutes
A baseline holding a lot of files is copied in on boot. provisioning is not a
terminal status. failed is, and veris status prints the reason beside it.
My sandbox disappeared
Its TTL ran out. veris up --ttl <minutes> sets the lifetime of a sandbox you
start, ttl_minutes in the environment’s entry in .veris/twin.yaml sets it for
every one, and the control plane’s own default applies when neither names a
lifetime. veris run --fresh --ttl <minutes> bounds the sandbox that run deploys,
which matters only if teardown never runs, since --fresh deletes it when the
command exits. veris status shows the expiry.
My test passed before and does not now
Check whether it hardcodes an id from an old sandbox. Ids are stable inside a sandbox, and across sandboxes only when the clock is frozen. Read ids at runtime.
Two runs interfered with each other
They shared a sandbox. Use veris run --fresh so each run deploys and deletes its
own, especially with webhooks, where the callback destination is one setting per
sandbox.
Something else
Tell us at hello@veris.ai. Include the sandbox id and, if you still have it, the request trace. Both make a report answerable in one pass instead of three.