xorcise doctor checks this host’s prerequisites and prints an exact fix per failure, and xorcise status probes the REST plane, the OTLP plane, and the Docker daemon.
Find your problem
Startup problems
Cannot connect to the Docker daemon
Cannot connect to the Docker daemon
xorcise up is real by default and needs a reachable Docker daemon.Fix. Start Docker — sudo systemctl start docker on Linux, or open Docker Desktop on macOS — and run up again. To look around without Docker, xorcise up --stub boots a demo in which missions do not execute.permission denied while trying to connect to the Docker daemon socket
permission denied while trying to connect to the Docker daemon socket
docker group. This is the standard fresh-Ubuntu case and it is not a XORCISE problem.Fix. Add yourself to the group, then start a new login session:newgrp docker is not enough — it changes only the shell you type it into, so xorcise up in any other terminal keeps failing.docker compose is reported missing
docker compose is reported missing
docker compose. The legacy standalone docker-compose v1 binary does not satisfy it, so a host with only v1 installed fails this check while docker-compose --version looks fine.Fix. Install the plugin:docker-compose-plugin package works equally well. Confirm with docker compose version.openssl is reported missing
openssl is reported missing
openssl is a hard prerequisite. XORCISE uses it to generate the private certificate authority and TLS certificate for the local Headscale control plane, so without it no run can get a network.Fix. Install it — sudo apt install openssl — and re-run xorcise doctor to confirm.port 3001 (rest) is busy — using 3002 instead
port 3001 (rest) is busy — using 3002 instead
up prints. Other XORCISE commands find the relocated brain by themselves. If you need a fixed port, pin it before starting with xorcise up --port 3010, or set rest_port in ~/.xorcise/config.toml. A port held by another program is stepped past in the same way. A conflict is reported only when a port is taken between the scan and the server’s bind, which names the port and plane and needs that process stopped (up then reports that XORCISE did not become healthy, and the message is in ~/.xorcise/serve.log), or when all 50 ports are taken, which stops up with no free port for <plane> in <first>-<last>.already running — UI at http://127.0.0.1:3001/ui
already running — UI at http://127.0.0.1:3001/ui
xorcise up against a running instance is success and exits 0 — that is deliberate, so scripts and agents can re-run it defensively. You do not need xorcise down first, and there is no restart to perform. If the previous process exited uncleanly, up clears the stale record and continues on its own. If the pid file is gone or stale but an instance of this home still answers on its REST port, up adopts that instance rather than start a second one over the same database, and prints already running (pid record repaired) — UI at … instead.database is behind the schema — stop any running XORCISE first
database is behind the schema — stop any running XORCISE first
db upgrade refuses while a server of this home is still running. The same status is visible on Settings → Environment, which reads behind — migration needed.migration history mismatch
migration history mismatch
xorcise.db alone can miss recent writes. Then either install the build that created it and upgrade from there, or re-initialise with xorcise down --purge, which destroys all local data. There is no downgrade path.XORCISE is running (pid …) and holds this database open
XORCISE is running (pid …) and holds this database open
xorcise db upgrade found a server of this home running, by its pid file or by asking its REST port, and will not migrate the database underneath it. Migrating rebuilds tables the server has open.Fix. Stop it first, then migrate and start it again:down cannot stop it, see the next entry. xorcise db upgrade --force skips the check, and is unsafe while a server is using the database.could not stop XORCISE (pid …)
could not stop XORCISE (pid …)
xorcise down signalled the server and it did not stop: the process belongs to another user, or it was still alive after SIGKILL. When that process is the one in the pid file, down leaves the file in place so the server stays findable. Either way it exits 1 before it reaps containers or tears down Headscale. Two related errors stop down the same way: could not stop the instance of this home on port …, for a server it found by asking the port rather than by the pid file, and an instance of this home still answers on port … but did not report a usable pid, for a server from before v0.2.0 that cannot tell down its pid.Fix. Stop the process yourself, as the user that owns it: kill <pid> with the pid the error names, or, when no pid was reported, find the process with the ss -tlnp command the error prints. Then run xorcise down again.port … accepted a connection but did not answer /api/system
port … accepted a connection but did not answer /api/system
up, down and db upgrade ask /api/system on each REST port an instance of this home could be using, to tell whether one is running there. Something on that port accepted the connection, then gave no answer within 8 seconds, dropped the connection, or answered with a server error. The commands cannot tell whether it is XORCISE, so they refuse rather than guess: a wrong guess starts a second server on the same database, or migrates the database under a live one.Fix. Find what holds the port: ss -tlnp 'sport = :3001' on Linux, lsof -nP -iTCP:3001 -sTCP:LISTEN on macOS. If it is a hung XORCISE, stop it; xorcise down can, while the pid file still names it. If it is another program, give XORCISE a different port with XORCISE_REST_PORT or rest_port in ~/.xorcise/config.toml. xorcise up --port alone does not get past it, because the configured port is still checked.the pinned Headscale image is not present and could not be pulled
the pinned Headscale image is not present and could not be pulled
xorcise up pulls it from Docker Hub when the host does not hold that exact image, and the pull failed: no registry access, a proxy Docker does not use, or a rate limit. The error names the image and ends with Docker’s own message.Fix. On a host that has registry access, pull the image the error names, save it, and load it here:up recognises a loaded image by its id, so it does not need registry access once the image is here. A headscale/headscale:v0.29.5 tag that names any other build is refused.the local Headscale runs a newer release than this XORCISE pins
the local Headscale runs a newer release than this XORCISE pins
headscale:stable named at the time, which can be newer than the pin; a later XORCISE can also have left a newer one. up checks this only while the old container still exists, which is the case after xorcise down --keep-data.Fix. Install the XORCISE release that pins the newer Headscale, or reset the control plane:xorcise down tears Headscale down with its tailnet state, which holds the nodes and keys of past runs, so no run should be in flight when you do it. Your runs, results and traces are in xorcise.db and are not touched.XORCISE did not become healthy in 10s
XORCISE did not become healthy in 10s
~/.xorcise/serve.log — the brain’s stderr goes there, and it is truncated on every up, so the traceback you want is the one at the top. ~/.xorcise/logs/ is created but empty; nothing writes to it, and it will not help you here. Running xorcise serve in the foreground reproduces the failure directly, and xorcise doctor re-checks the prerequisites.The web UI loads blank or unstyled
The web UI loads blank or unstyled
/ui is stale or half-built. This only happens in a source checkout — an installed package ships the built UI.Fix. Confirm the brain is healthy with xorcise status and reprint the address with xorcise ui. up rebuilds the UI automatically when it needs to, so if the page is still broken, read ~/.xorcise/frontend-build.log for what the build said.Run and telemetry problems
The run sits on 'Action required — start your agent' or 'Awaiting agent'
The run sits on 'Action required — start your agent' or 'Awaiting agent'
xorcise run launch-cmd <run_id> — and check the entries below. If the fact strip shows the environment as Starting, wait for Ready before launching: an agent that joins early ends up on a tailnet whose targets do not exist yet.The run reads 'Xorcising' but sits on 'Awaiting traces'
The run reads 'Xorcising' but sits on 'Awaiting traces'
Run creation refuses on nested containers
Run creation refuses on nested containers
request failed (503): and exits 1.Fix. <what to do about it> is one of three fixes:this mission's image is <platform>, but this host executes <host> natively and cannot run <platform> containers nested. The mission has no image for your host’s platform, so it would run under emulation, and this host cannot nest an emulated container. The usual case is anamd64-only mission on anarm64Linux host. With no emulator installed, the probe’s container cannot start at all (exec format error). With a qemu emulator, it starts, but the Docker daemon inside it fails to set up its firewall (iptables: Failed to initialize nft). No setting changes this: choose a mission that publishes an image for your host’s platform, or run this one on a host of the platform it names.enable Rosetta for x86/amd64 emulation in Docker Desktop. An Apple Silicon Mac was asked to run anamd64image and Rosetta is off or failing. Turn on Docker Desktop’s Rosetta setting, make sure the VM is the Apple Virtualization framework, because Docker VMM has no Rosetta, then restart Docker Desktop.Check that Docker can run privileged containers, then re-run 'xorcise doctor'. This host cannot nest even its own platform.
doctor, never under xorcise up, because a host that cannot nest can still serve the web UI and run static missions.Run creation reads the verdicts the running server holds, and the server keeps each platform’s verdict, refusals included. When XORCISE is running, doctor asks the server to probe again and shows what the server now holds, so re-running xorcise doctor after you fix the host clears a cached refusal without a restart. It probes your host’s own platform and every other platform a run has asked about, each on its own nested containers (<platform>) line, then create the run again. With XORCISE stopped there is nothing cached to clear, so doctor probes in its own process, and only your host’s own platform. A server from before 0.2.1 cannot probe on request: doctor says so, and the fix is still xorcise down and then xorcise up.Set XORCISE_NESTED_CONTAINER_CHECK=skip only where the probe cannot run but nesting is known good — restricted CI, no privileged containers. It bypasses the check, not the requirement: a host that genuinely cannot nest will still fail, later and less clearly. XORCISE reads the setting when it starts, so it takes effect only after xorcise down and xorcise up, whether you set the variable or nested_container_check in ~/.xorcise/config.toml.The control plane address is not answering
The control plane address is not answering
headscale_url. xorcise up built that URL from the host address it detected when it provisioned Headscale: the Docker bridge gateway on Linux, the LAN address on macOS, or XORCISE_HEADSCALE_HOST_IP if you set it. If that address changes afterwards, for example when a Mac joins another network or gets a new DHCP lease, the Headscale container stays healthy but nothing answers at the recorded address. No router can log in, so every lab run ends deploy_failed at the readiness window, and its recorded reason begins:xorcise run status prints that reason, and the run page shows it. It appears whenever a run’s router never joined the tailnet, so a moved address is the likeliest cause, not the only one.Fix. Confirm it with xorcise doctor. Its control plane address check probes the recorded URL and, when the address up would detect today is different, names both:up detects the address again and provisions the control plane on it:down removes every run container, including those of runs still in progress, so let those finish first. If you set XORCISE_HEADSCALE_HOST_IP, up and doctor both use it instead of detecting the address, so change it to the new address before up. A failure that names no current address means XORCISE could not detect one, for example because Docker did not answer within five seconds. up runs the same detection and says what stopped it.No traces arrive at all
No traces arrive at all
host.docker.internal into the telemetry endpoint, and a plain terminal cannot resolve that name — the exporter fails silently and not one span arrives.Fix. Ask for the mode that matches where the agent actually runs:--add-host host.docker.internal:host-gateway instead.Two other causes account for most of the rest. A bare KEY=VALUE paste is not inherited by the child process — use set -a; source launch.env; set +a, or paste the single launch block, which already does that. And xorcise run launch-profile printing nothing at all means no collector host is configured for this run.partialSuccess.rejectedSpans in the exporter's response
partialSuccess.rejectedSpans in the exporter's response
…or run is sealed in both cases, and it is the definitive signal for this class of problem. Rejected batches are dropped, never blended into another run.Fix. Work down these in order.xorcise.run_idwas set as a span attribute. Only the resource attribute correlates; set it throughOTEL_RESOURCE_ATTRIBUTES.- You already exported
OTEL_RESOURCE_ATTRIBUTESand overwrote it. Appendxorcise.run_id=<id>comma-separated instead. - The prompt’s
xorcise.run_id=<id>marker line was deleted. For a harness with no telemetry provider that marker is the only correlation channel there is. - The run is already terminal. When a run ends, XORCISE keeps ingestion open for a short drain window,
telemetry_drain_seconds(five seconds by default), then seals the run and rejects everything after that, by design. A rejected batch is dropped, never stored or graded, and a sealed run cannot be added to, so the lost tail cannot be backfilled. On the next run, have the agent flush its telemetry before it reports completion, shorten its export interval, or raisetelemetry_drain_seconds. - The prompt was copied from an earlier run, so the marker names that run.
curl http://127.0.0.1:4318/healthz (or the OTLP port xorcise up printed) returns a drops object, whose unroutable_* counters cover the first three causes above and sealed_* the last two. ~/.xorcise/serve.log has one otlp drop: line per refused batch. Send traces and logs explains each counter.Spans arrived but the replay is empty
Spans arrived but the replay is empty
The replay warns about unclassified spans or missing content
The replay warns about unclassified spans or missing content
span(s) matched no classification rule means the generic adapter rendered the run and recognised none of those span names, so it shows them as they arrived rather than as tool calls. none of the … carries a content-bearing attribute means the spans and log records carried names and ids but no commands, output or text, so the judge’s transcript was empty and it graded from the submitted artifacts alone.Fix. Fix the harness, then start a new run: both warnings describe what was sent, so a run already recorded cannot be repaired. If your harness is Claude Code, Codex or OpenHands, register the agent with that kind so its own adapter is used. For Claude Code, check the OTEL_LOG_* variables. When you drive the OpenHands SDK from your own code, export the variables before importing openhands.sdk. Send traces and logs has the per-harness detail.The join stops: the tailscale client is too old to read its auth key from a file
The join stops: the tailscale client is too old to read its auth key from a file
tailscale up as a file, never as a command-line argument, because any user on the machine can read a process’s arguments with ps. The tailscale already on the agent’s PATH predates reading the key from a file, so the script stops rather than expose the key.Fix. Upgrade that client. On Linux you can instead run the pinned client the script fetches into its scratch directory, which installs nothing:The join refuses with 'refusing to keep the join key under …'
The join refuses with 'refusing to keep the join key under …'
XORCISE_TS_DIR, or under $TMPDIR or /tmp when that is unset. The named directory is owned by someone other than you or root, or other users can write to it without the sticky bit /tmp has, so one of them could swap the script’s directory for their own.Fix. Point XORCISE_TS_DIR at a directory only you can write to, or unset it. See Settings for the join script.Every run-control call answers 401 after the launch command
Every run-control call answers 401 after the launch command
$XORCISE_RUN_BEARER rather than carrying the run’s token, and the launch block exports that variable just before the command. The agent did not inherit it. That happens when only the command line was copied without the export lines, or when the command starts the agent in a container or on another machine and does not pass the variable through. Every call then sends an empty token and gets {"detail": "invalid run-control credential"}.Fix. Run the whole block from xorcise run launch-cmd <run_id>, or Copy launch command on the live page. If your command starts the agent elsewhere, pass the variable through, for example docker run -e XORCISE_RUN_BEARER. To start an agent by hand instead, give it the prompt from xorcise run prompt <run_id>, which carries the token inline.The agent joined the tailnet but cannot reach a target
The agent joined the tailnet but cannot reach a target
$XORCISE_SOCKS5 in the sourceable env file it writes. Do not hardcode a port, and configure the proxy explicitly in tools like pwntools rather than assuming a direct connection.The environment never becomes Ready
The environment never becomes Ready
deploy_failed. A run that ends this way is not graded: it has no score, and it is left out of every median and rate rather than counted against the agent. The same trigger also marks a run whose deploy never finished; see the not graded entry under Grading.Fix. Read what XORCISE recorded when it closed the run out: the live page shows it under the run’s status, and xorcise run status <run_id> prints it. The first line is the readiness gate’s verdict — the environment failed with the exit code it died with, or not ready within the readiness window with what was still missing: mission services still starting, an inner Docker daemon that never answered, or a subnet router that never joined the tailnet (for that one, see The control plane address is not answering above). Below it is the tail of the run container’s log, which is where a failed compose up says why, and the inner Docker daemon’s log while the container was still running. While the run is live, the environment chip’s tooltip carries the same reading. Every run loads its mission’s images inside its own container before the services start, which takes longer for a large mission on a busy host; if runs are closed out while they are still coming up, start fewer at once or raise readiness_timeout_seconds above its default of 300 seconds. If it fails repeatedly, the mission’s compose file is the place to look.The Environment chip reads Teardown failed
The Environment chip reads Teardown failed
container: is Docker, tailnet: is Headscale. Each half is released on its own, so the other one is normally gone already. The same state shows as environment not released under xorcise run status, as a releases: line in xorcise system, and as releasing with a non-zero release_attempts from the REST API. The recorded result is unaffected: the teardown runs after it.Fix. Bring the named plane back. Start Docker if it is down. For the local Headscale, xorcise doctor checks the control plane. A container that was only stopped comes back with docker start headscale (the name is headscale_container). Otherwise restart XORCISE with xorcise down, then xorcise up, which provisions it again — but down also removes the containers of any run still in progress, and the next start closes those runs out as crashed. xorcise up alone does nothing while XORCISE is already running. You do not need to touch the run: XORCISE retries the teardown on its own until it lands, backing off per run from 30 seconds to at most 10 minutes between attempts, and xorcise system stops listing it once it is released.A delete is refused: the environment could not be torn down
A delete is refused: the environment could not be torn down
--force to xorcise run delete or xorcise agent rm, or choose Delete anyway in the web UI. That accepts the leak: the container stays until XORCISE next stops or starts and reaps it, and nothing is left that can remove its tailnet nodes. A delete refused because a run has not finished or is still being graded cannot be forced — terminate the run, or wait for its result.Grading problems
The result says the judge did not run
The result says the judge did not run
judge_status reads model-not-configured: no judge model is set, so XORCISE never called one. The judge half scored 0.0, and because that zero is still multiplied by 0.5, the run’s ceiling was 50%. A score of 42.5% here is half a score, not a failed agent. In the terminal, xorcise run status prints JUDGE DEGRADED — model-not-configured under the scores, xorcise run list marks the score with *, and xorcise leaderboard counts the run under No judge and leaves it out of the median.Fix. Configure a judge model — see Configuration — then re-evaluate the run. Re-grading replays the already-sealed evidence against your current settings. Do not run the agent again; nothing about the run needs to change.The judge failed on a long run
The judge failed on a long run
judge_status reads unavailable and judge_detail carries the provider’s own message, usually about context length. A long lab run produces an enormous transcript — a measured ten-minute run produced a 369,000-token prompt against a 272,000-token ceiling — and when the call cannot be made, the score degrades to deterministic-only. xorcise run status prints JUDGE DEGRADED — unavailable under the scores, with that message in parentheses.Fix. Lower the per-span cap, which defaults to 2000 tokens and trims each span to a head-and-tail window while keeping every action:--transcript-max-tokens 0 to switch it back off and let the provider’s real limit be the gate.The result reads 'grading' instead of a score
The result reads 'grading' instead of a score
xorcise run status <run_id> re-checks from the terminal. Polling also re-drives grading that was lost to a restart, so a run stuck in this state past the judge’s timeout usually resolves by being asked once more.A run reads 'Cancelled' or 'Never started'
A run reads 'Cancelled' or 'Never started'
not graded, it cannot be re-evaluated, and it is left out of every performance view. This is final: xorcise run status exits 0.Fix. Find out why the agent never checked in. Check that it was launched against this run, with this run’s prompt and launch profile — Copy launch command on the live page, or xorcise run launch-cmd <run_id> — and that it can reach the run-control address in the prompt; launch mode is the usual cause, covered under No traces arrive at all in Run and telemetry problems. Then start a new run, and watch it move from Created to Xorcising once the agent starts. Give it a budget long enough to cover the time before you launch it, because the budget counts from creation.The result reads 'not graded'
The result reads 'not graded'
deploy_failed or crashed: an environment failure, not the agent, cut it short. deploy_failed means the mission environment never became ready or failed while the run was live — or that the deploy never finished, because the create failed or XORCISE stopped mid-deploy, and tearing down what it had started failed too. crashed means the run’s environment was gone when XORCISE restarted — a xorcise down, or a host or Docker restart, while the run was live. Neither is graded, so there is no score to wait for, and the run is left out of every median and rate. This is final: xorcise run status exits 0, not 3.Fix. Nothing about the agent needs fixing. Read the reason printed under the status, and the evidence recorded with it for a deploy_failed run, then start a new run. Re-evaluating does not help: the server refuses to grade such a run. For a crash, keep XORCISE and Docker up until a run finishes.The likeliest way to get a deploy_failed run whose deploy never finished is Headscale stopping partway through a run create. A Headscale that is already down fails the create with a 503 before anything is reserved, so it leaves no run. One that stops after that check fails the teardown too, so the run is kept as deploy_failed and its reason names the create’s error. The release sweeper retries the teardown until the environment is gone, and until then xorcise system counts the run among the finished runs that still hold their container or tailnet nodes. Once released, the run stays in the run list until you delete it.Mission and library problems
xorcise mission list, install one with xorcise mission pull <id>, or use the Missions page. xorcise mission ingest is a stub that prints the coming-soon message and installs nothing, whatever arguments you give it, and the Ingest a bundle button on the Missions page opens a product preview rather than a file picker. The manifest rules below describe the format that feature will accept, so you can prepare bundles now — but nothing in this release installs a mission from a local bundle.Library missions do not appear
Library missions do not appear
xorcise catalog connect, or the XORCISE Remote switch on the Settings page. The library needs no API key, account, or sign-in, so a missing credential is never the cause here.A pull sits at 0 bytes
A pull sits at 0 bytes
3 — the pull is still running on the server.A mission reads Update available
A mission reads Update available
Doctor warns that disk space is low
Doctor warns that disk space is low
xorcise doctor warns only … GB free at … and suggests reclaim space with: xorcise mission prune.Fix. List the mission images nothing uses any more, then remove them:1. The sizes it reports are an upper bound, because images built on the same base share layers.Do not use docker system prune -a for this. It removes every unused image on the host, not only XORCISE’s.Releases before 0.2.1 also left a Docker volume behind for every lab run: the run container’s own image store, often several GB. XORCISE now removes it with the run’s container, but the volumes earlier runs left carry no label, so XORCISE cannot tell them from anyone else’s and never removes them. docker system df -v shows them under local volumes, and docker volume prune removes the volumes no container uses. On Docker 23 and later that means only anonymous volumes; on older Docker it removes named ones too, so check the list first.This mission was built on an older base
This mission was built on an older base
xorcise doctor: the library promotes a base generation newer than your install, so newly pulled missions would be refused. Upgrade XORCISE with pip install -U xorcise.A mission has no image for this host's architecture
A mission has no image for this host's architecture
linux/amd64, XORCISE runs it under your host’s emulation layer. The server logs a notice for the pull, Native ARM64 image unavailable for this mission. Running the AMD64 mission using compatibility/emulation mode., but the CLI never prints it. That is not an error. On an Apple Silicon Mac with Docker Desktop’s Rosetta on, the mission runs, more slowly. On an arm64 Linux host it does not: xorcise mission pull, xorcise run create and the web UI still download and install the image, but creating a run of it is refused, because this host cannot run an amd64 container nested. See Run creation refuses on nested containers under Runs and telemetry.To pin every pull and run to one platform regardless, set XORCISE_DOCKER_PLATFORM. It overrides the selection unconditionally, including for a mission that never validated that platform, so use it to reproduce a specific result rather than as a default.What a valid mission.json must satisfy
What a valid mission.json must satisfy
schema_version must be 2.0 or 3.0, and a 3.0 manifest must carry the mission’s own SemVer in version while a 2.0 one must not. metadata.type is required and accepts only lab or static — the older genre labels such as ctf are dead. A lab mission requires an environment block, a static mission requires at least one attachment, and check weights must either all be declared and sum to 1.0 or all be omitted.Reading a validation error. A manifest that breaks any of those rules fails as ingest failed: invalid mission.json, and the messages are literal — they name the field path, for example invalid mission.json: checks.0. Compare that field against Mission manifest, which is the authoritative shape. Missions installed with xorcise mission pull are already valid, so nothing you install today can fail these rules.Exit codes in scripts
XORCISE uses five exit codes:0 success, 1 runtime failure, 2 usage error, 3 operation still in progress, and 130 interrupted.
3 is the one that breaks naive automation. run status, run report, run terminate --wait, and mission pull return it when the work is still running server-side. A CI gate that treats every non-zero code as a failure reads a perfectly healthy in-progress run as broken. Poll until you get 0.0 also covers idempotent no-ops: up when already running, mission pull when the mission is already installed, and catalog connect when already connected. --json is available on 22 commands, and run status --json is always parseable because it always exits 0 and its envelope carries a status of created, xorcising or grading rather than a score.
Start clean
When you want to discard everything and begin again:~/.xorcise. Run it before pip uninstall xorcise if you are removing XORCISE entirely — the purge is what cleans up the Docker resources, and once the package is gone nothing knows how to find them.
File a useful bug report
Include five things and most reports become reproducible immediately: the output ofxorcise --version, the full output of xorcise doctor, the run id if a run is involved, the exact message you saw rather than a paraphrase of it, and the last few lines of ~/.xorcise/serve.log when the brain failed to start. If XORCISE printed unexpected error: … — re-run with XORCISE_DEBUG=1 for the full traceback, do that and attach the traceback.