Troubleshooting
Start every investigation with the same two commands:
harness doctor # is the config loaded, the daemon reachable, anything degraded?
harness describe NAME # state, restarts, last exit, flapping, schedule
Then find the symptom below.
The agent is running but does nothing
harness list shows ● running, and nothing happens. This is the most common
failure, because it looks healthy. Attach and look before changing anything:
harness attach NAME --ro
Usual causes, most likely first:
-
It is waiting at a prompt nobody can see. First runs of Claude Code ask you to log in and to trust the folder, and an agent can stop to ask for tool approval. Attach read-write (
harness attach NAME), answer the prompt, and detach withCtrl-b d. For unattended work, run the agent once by hand in the sameworkdirfirst (first agent). -
Its credentials expired or never arrived. A logged-out agent keeps running and fails every request. Check as the same user the daemon runs as. For Claude Code:
claude auth statusIf you pass keys through an
env_file, check the path. A missingenv_fileis silently skipped, so a typo means the agent starts with no key at all. After fixing either,harness restart NAME, because env files are read at start. -
It never received its events. An always-on Switchboard worker idles until a doorbell arrives. If none arrive:
- Is the channel enabled? Crush needs
--channels server:NAMEorchannel_enabled: true, from a build that has the Streamable HTTP channel fix (details). Listing the server undermcpalone does not push. - Did the MCP server connect? An empty
$SWITCHBOARD_TOKENdrops theAuthorizationheader, and the server rejects the connection. Attach and check the agent's MCP status. - Is another session swallowing the doorbell? Switchboard rings one session per todo. An interactive session, or a second copy of the worker on another machine, using the same endpoint can take the ring and do nothing. See one channel consumer per server.
- Does it drain on startup? Doorbells sent while the worker was down were
dropped; their todos are still pending. Switchboard re-rings them, but only
after about 5 minutes, then 20 minutes, 1 hour and 6 hours, and it stops
after a fixed number of attempts. The worker's instructions should call
claim_nextuntil empty when it starts.
- Is the channel enabled? Crush needs
-
It's a headless Claude Code channel consumer. That setup isn't verified to act on doorbells, and the development-channels flag waits for an interactive confirmation. See the Claude Code status.
It keeps restarting
harness list shows ◐ degraded or ◌ restarting, and the restart count
keeps climbing.
harness logs NAME --raw --lines 40
harness describe NAME # last_exit, flapping
-
last_exit -1with no output at all. The process never started, and almost always the executable is not on the daemon'sPATH. A service gets a minimalPATH, and a harness'senv_filecan't fix the executable lookup. Current builds don't log this spawn error, so check the path yourself:command -v claude crush codex # where they live in your shellsystemctl --user show harness -p Environment # the PATH the daemon has (Linux)Add the missing directory to
Environment=PATH=…in the unit, or toEnvironmentVariablesin the plist, then restart the service. See the service guide. -
A
genericharness exits immediately.argsgo tosh, so a bareargs = ["/usr/local/bin/thing"]asksshto read that binary as a script. Useargs = ["-c", "/usr/local/bin/thing --flag"]. -
A flag the CLI rejects. The agent prints its usage and exits 1. Read it with
harness logs NAME --raw: for an agent harness, the default view shows agent activity, so a failure before any session started leaves onlynotelines there. -
A real crash loop. After three exits within ten seconds the harness is
flapping, and restarts back off.
What to do right away:
-
Stop anything looping, especially a metered agent:
harness stop NAMEThe supervisor is designed to give up and park a harness in
failedafter repeated failures, but current builds don't apply that limit. A broken harness keeps retrying until you stop it. -
Once it's fixed, clear the state:
harness restart NAME. That also clears afailedstate. -
Prevent the next one: use
restart = "on-failure"with arestart_delayof 30 seconds or more for agents (why).
A schedule didn't fire
Check whether the daemon knows about the schedule at all:
harness jobs # every scheduled harness: schedule, next run, last run, failure streak
harness runs NAME # what each window did: success, failed, skipped, missed…
harness describe NAME # the cron spec and the absolute next run
- It isn't in
harness jobs. The config didn't load, or the harness is in a drop-in the daemon hasn't read.harness doctorshows a parse error with its file and line. For drop-ins, runharness reload(see the config didn't reload). - The machine was asleep or the daemon was down at that time. The window
was missed.
harness runs NAMEshows amissedrecord, the daemon log has ascheduled run MISSEDwarning, and nothing ran becausecatch_updefaults tofalse. Setcatch_up = truefor work that should run once on wake. - It fired at the "wrong" time. Without a prefix, a schedule uses the
daemon's local time zone.
harness describeshows the next run; compare it to what you expected, and pin the zone withCRON_TZ=UTC(why). - The previous run was still going. With
on_overlap = "skip", the default, a window that fires mid-run recordsskippedand does nothing. A run that never ends skips every later window.harness runs NAMEshows theskippedrecords, andharness jobsshows the stuck run asrunning #N. Set atimeout. - It fired, and the run failed fast.
harness runs NAMEshowsfailedwith its exit code, andharness logs NAME --run Nshows what it did. Work through it keeps restarting for the cause. - It ran on a different machine. A synced config fires on every host running a daemon. Keep scheduled harnesses in a per-host drop-in directory (details).
To rule out the schedule entirely, run it now and watch:
harness trigger NAME --wait
echo $? # the run's exit code; 124 = timed out, 75 = skipped (a run was in flight)
The config didn't reload
- The file has an error. A config that fails to parse is not applied. The
daemon keeps running the last good config.
harness doctorshows the error with its file and line; fix it and save again. - You changed a drop-in. Only
harness.tomlitself is watched. Files inharness_dneedharness reload. - You changed a running harness. Reload applies to what the daemon will
run. A running harness keeps its old settings until it restarts, so an edit
never kills an agent mid-task.
harness describe NAMEshowsconfig — changed — restart to apply. Runharness restart NAME. - You changed an
env_fileor aprompt_file. Env files are read when a harness starts, so runharness restart NAME. Prompt files are read at every run, so the next run picks the change up with no action. - Watching is off.
[daemon] watch_config = false, orHARNESS_WATCH_CONFIG=falsein the service environment, disables auto-reload. Useharness reload, orkill -HUPthe daemon. - The daemon reads a different file than the one you edited. The
configrow ofharness doctorshows the path the daemon loaded. AHARNESS_CONFIGor--configin the service definition overrides the default.
harness says the daemon isn't running, but it is
The client and the daemon disagree about the socket path. That usually means
your shell and the service see different XDG_* variables, most often on
macOS. Pin HARNESS_SOCKET to the same short path in both
(details).
The daemon exits at startup with bind: invalid argument
The socket path is too long: about 104 bytes on macOS, 108 on Linux. It happens
when XDG_RUNTIME_DIR or XDG_STATE_HOME points somewhere deeply nested. Set
HARNESS_SOCKET (or --socket) to a short path, such as
~/.local/state/harness/harness.sock.
Still stuck?
Collect harness doctor, harness describe NAME, harness logs NAME, and the
daemon's own log from around the time of the problem. Read --raw logs for
secrets before sharing them; see
the durable log.