roost doctor
The report starts with the roost version you are running, so paste the whole report into any bug report — or at least its top. Then it checks your tmux version, truecolor support, fzf, the notifier, the Claude hooks, the opencode plugin link, and the GitHub Copilot CLI extension link — and prints the exact fix command for your checkout. For copilot it also checks the feature flag that extensions sit behind, and reminds you of a consent prompt it cannot check from disk (see below).
The view works without badges; badges need a hook or an adapter. The one
command that fixes most of this is roost install — it wires every installed
harness to your checkout, is safe to re-run, and roost doctor ends every
warning it can resolve with , or run: roost install. A warning without that
tail is one no installer may fix: something that is not roost's sitting at an
adapter path, a hook pointing at a different checkout, or a prompt only you can
answer. What it does not do is fetch new roost code — if the adapter you
need shipped in a later release, upgrade first by re-running the install
command (Getting started). Run that from a directory
that is not itself a roost clone, and check that the path in its first two
lines is the checkout your agents are wired to. If roost install has been run
and a pane still does not badge, check, in order:
roost hooks output is merged into ~/.claude/settings.json under "hooks". See State Badges.scripts/roost-agent-state is a deliberate no-op outside one.roost doctor, which prints the exact ln -s for your checkout. See State Badges. If your opencode is 2.x, stop here: it is not supported yet, roost doctor says so, and the opencode section of State Badges has the detail.~/.codex/hooks.json is written and you have answered "Trust all and continue" at codex's own Hooks need review prompt. That answer is one of the two steps roost install cannot do for you.roost state <state> itself.roost doctor, run in the pane, names the state:
! claude in this pane resolves to …, not roost's shim. Something put
another claude first: a default-command in your own roost.conf, or your
shell's startup files. The pane still badges if you ran roost install.type claude in the pane says
claude is an alias, the alias skips roost's shim, even when doctor says the
shim is first.· ROOST_NO_SHIM is set here, · wiring is off for this roost server
or · wiring is removed. You, or a script, turned it off. roost wiring on turns it back on; for ROOST_NO_SHIM, unset it.! the claude shim cannot read the server switch here. This pane has
neither ROOST_TMUX nor tmux on its PATH, so roost wiring off cannot
reach a claude started in it. Restart the roost server, or put tmux on
PATH.! the global Claude hooks name a different checkout. Every Claude hook
runs twice. Run roost install from the checkout this server runs, or turn
roost's wiring off.To run claude once with only your own settings: ROOST_NO_SHIM=1 claude.
Two gates stand in front of the copilot extension, and neither one tells you when it is not met — the turn runs normally and the pane just stays blank. roost doctor checks the first; the second it can only remind you about.
roost install turns the flag on for you. By hand: launch your panes as copilot --experimental, or put {"enabledFeatureFlags": {"EXTENSIONS": true}} in ~/.copilot/settings.json. Without one of them copilot never reads the adapter.Take the blank badge seriously rather than living with it. roost send refuses a 🛑 blocked target so one agent cannot paste into another's permission dialog — and an unbadged pane is not blocked, so that refusal never fires. A copilot pane whose extension never loaded, sitting at a permission prompt, will take a roost send straight into that dialog.
Your Notification hook is missing the permission_prompt matcher. Unmatched, it also fires for idle_prompt and auth_success, so a finished agent goes blocked. Re-run roost hooks and compare.
A PostToolUse or PostToolUseFailure hook is missing. No hook fires when you answer a permission dialog, so those two are the first observable events after approval — they are what clears 🛑. You need both: PostToolUse fires only when the tool succeeds, so with it alone a command that exits non-zero after you approved it leaves the window red for the rest of the turn. roost doctor tells you when either is missing; roost install adds the missing entry and leaves the rest alone.
Your StopFailure hook is missing. When a turn ends on a rate limit, an overload or another API error, Claude fires StopFailure instead of Stop, and without that hook nothing moves the badge off ⏳ working. Run roost install again — it adds that one hook and leaves the rest alone. roost doctor tells you when it is missing.
If the hook is there and the pane still reads working, look at the pane: Claude may still be retrying. It retries a rate limit for a few minutes before it gives up, and the badge turns 💥 when it does.
roost send failsThe exit code tells you which failure it is:
send refused rather than pasting into a permission dialog. Answer the dialog and retry the same target. If the pane has no dialog on it, see A pane refuses every send but has no dialog below.roost send: message — delivery to a valid target failed. Retry the same target or look at the pane; do not re-resolve. Two of these name their own cause: could not confirm the message reached (see Only the end of my message arrived below) and message too long for tmux, which needs a file and a path instead.usage: message — a missing argument. That is a caller bug.You sent an agent a long briefing and it answered as if it had been given only
the last part of it, with roost send reporting success.
Typed at speed, an agent's input box can keep only the last chunk of a large message and discard the front. Measured against Claude Code 2.1.263: a 3502-byte message arrived as its last 436 bytes.
Current roost sends a bracketed paste instead of typing, which the agent
treats as one block, and the whole message arrives. If you are seeing this, the
machine is on an older roost — run roost doctor.
The workaround for an older machine is the one people arrived at anyway: write the briefing to a file and send the path.
[Pasted text #1] instead of the textNothing is wrong. That is how the agent renders a pasted block. The full message is submitted; only the on-screen rendering is a placeholder.
roost send exits 3 and says a permission dialog is open, but the pane is sitting at an empty prompt.
The badge is stale. A turn that ends at a permission dialog — you answered No, or pressed Esc — fires no PostToolUse, so nothing that clears 🛑 after Yes runs.
Usually roost now clears it for you:
Interrupt event on No and Esc, and roost's hook badges the pane 💤 idle. It only works once codex trusts that hook: if you trusted roost's hooks before it was added, answer "Trust all and continue" at codex's "Hooks need review" once more.roost send, read and wait-done read that transcript and clear the badge when the decline is the newest thing in it. This needs the hooks from a current roost install (the Notification hook carries --notification-hook), and python3 or jq.If the badge is still red, one of those did not hold — or it is a harness roost cannot check.
Check it rather than assuming, and read the badge and the screen in the same moment — a badge you read seconds before a screen tells you nothing:
roost status | grep '%12' # the badge
roost screen %12 20 # what is actually on the pane
If there is no dialog on the screen, any of these clears it:
UserPromptSubmit re-stamps itroost send --force %12 "…" — safe once you have looked and seen no dialogroost state working from inside that paneroost wait-done will not rescue you from a badge it cannot prove stale: it counts blocked as busy, so it waits out its timeout. roost doctor names every pane that has read blocked for ten minutes with no dialog on screen, as "may be stuck", and changes nothing. What is still not covered is recorded in docs/known-gaps.md in the repo.
roost wait-done exits non-zeroExit 1 means error or timeout, distinguished by the message. An errored pane prints roost: '<target>' is in error state, not done and exits 1. Exit 2 means the agent died — its pane closed or its process exited mid-turn — or the target was already gone. wait-done deliberately does not count "stopped being busy" as success. The full table, and the kinds of death it cannot see, are in Driving a fleet.
prefix a does nothingThe agent switcher needs fzf. Without it, the binding degrades to a hint. Install fzf and it works.
If it opens as a plain list — no preview, no reload — your fzf is older than the live switcher needs. roost doctor names this; upgrading fzf fixes it.
You need a powerline or Nerd Font. Or run roost settings and pick the plain-separator style — it applies live, no restart.
There are two different warnings here, and each says only what it found in your config.
"has no @roost-glyph-error line" — you picked ascii or nerd glyphs before the error state existed, so your config has four glyph lines and the error badge falls back to the built-in 💥: an emoji in a bar you chose not to have emoji in. Fix: re-pick your glyph set in roost settings, which writes all five.
"sets @roost-glyph-error to ... where the set's own error glyph is ..." — your config has an error glyph that is not the one your set uses. If you chose it yourself, there is nothing to fix. If you did not, re-pick your glyph set in roost settings.
Neither ever fails doctor.
roost init fills the missing glyph in for you when it migrates an old ~/.config/amux/amux.conf — but only when your other four glyphs exactly match one of the named sets, so a hand-picked set is never overwritten with something you did not choose.
Only blocked and error notify. done fires every turn and would be noise, so it is deliberately silent.
If neither fires, set @roost-notify-backend explicitly (tmux always uses the in-tmux message) or plug in your own @roost-notify-cmd. See Notifications.
roost validate
roost doctor reads your configuration. roost validate drives it, and writes the report for you — the environment and every version, roost doctor verbatim, both offline test suites with their exit codes, and a real end-to-end drive of every agent harness you have installed: it starts each one in a throwaway roost server, asks it a question, samples the badge over time, checks that roost read returns the reply where roost screen returns the screen, and drives a permission dialog where the harness has one. It ends by printing one file to send us. Nothing in it needs filling in.
Each harness runs the way you normally run it — your own configuration, your provider, your account. That is the point: a small local model barely calls tools, and tool calls are what drive PostToolUse, permission dialogs and the ⏳→🛑→⏳ transitions the badges are almost entirely about. So the run costs you a little money — roughly three one-line prompts per harness — and it says so before it starts.
No accounts at all? roost validate --opencode-cloud drives opencode against one of opencode's own free cloud models. Those need no account and no key — opencode auth list can report zero credentials and they still work — and they are a real remote provider, so unlike a local model they have real network latency and call tools readily. For opencode this is the better fallback: measured, a free cloud model reached a permission dialog in 3–4 seconds where a local 3B model took 55 and often would not call a tool at all. It affects opencode only, and costs nothing.
roost validate --local drives a local ollama instead and spends nothing. It is the fallback for a machine with no accounts wired, and it tests our rig rather than your setup; the report records which mode each harness actually used, because a report that does not say that is nearly worthless to us.
It takes a while, so start it and walk away. Every wait is bounded, so it cannot sit there forever, and it writes the report even when something fails. A wait that runs out is reported as a timeout, not as a failure — against a real account a turn with tool calls can take minutes.
It asks one question first. If a roost adapter symlink is missing — the install step roost doctor already prints — it lists what it would create and asks once, for all of them together. Say yes and it links them and tests those harnesses; say nothing and after 60 seconds it takes that as no and skips them, exactly as before. It creates symlinks and nothing else: no hooks file, no codex trust, no settings change, no provider configuration. The report lists every link it made with a one-line rm to undo it. --install and --no-install answer it up front for an unattended run, which otherwise defaults to no.
What it will not do:
--keep-home-paths if you would rather send it raw.Claude Code is the one harness it will not drive: there is no way to isolate its configuration without taking the credential with it.
One thing worth knowing before you read your own report: the real-provider path has never run on our machines, because none of them has an account wired for these harnesses. Everything we tested took the --local path. If a result looks odd, treat it as a genuine finding and tell us rather than assuming it is your setup.
roost validate --quick skips the slowest part (this repo's own ollama-based smoke suites) and still produces a full report.
Then open an issue at github.com/beatzball/roost and attach it. Maintainer-facing notes on shipped risks live in docs/known-gaps.md in the repo.