Skip to Content
ReferenceTroubleshooting

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 version

Not logged in for profile 'default' (no API key)

veris login

It 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-stdin

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

  1. Your suite never called the dependency. Mocks still active, tests filtered out, wrong path.
  2. The traffic went to the real vendor, because that host has no twin in your environment. veris services lists the catalog and veris env get the environment; --strict turns the leak into a 421 instead of letting it out.
  3. TLS trust failed inside your container, so no request completed. See below.
  4. Nothing was intercepted for that twin at all, and its URL was handed to your command. See below.
  5. 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> --yes

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