Warp-Drive Guide
Warp Drive
Section titled “Warp Drive”Autonomous development loop for Claude Code. Picks up approved work from GitHub Issues, breaks it into chunks, codes, tests, commits, and reports — with minimal human intervention.
Two ways — but they do different jobs. The slash command runs the loop (it
needs a live Claude Code session); the terminal bob warp command only observes and
controls a loop that is already running — it cannot start one.
Slash command — run the loop (in a Claude Code session):
/warp-drive 42 # work on issue #42/warp-drive # auto-discover next approved issue/stop-warp-drive # graceful stopTerminal — observe / control a running loop (any shell):
bob warp status # check on the loopbob warp stop # abort from outsideWhere It Fits
Section titled “Where It Fits”Warp Drive operates at the bottom of the product hierarchy (vision → capability → requirement → chunks). It picks up requirements (not capabilities or vision) and turns acceptance criteria into commits, one commit at a time.
This page is how to run it. How it works inside — phases, hooks, breakers, the monitor, configuration — is Warp-drive internals. Every state-machine subcommand and flag is in the generated flag reference.
Installation
Section titled “Installation”Prerequisites
Section titled “Prerequisites”| Dependency | Check | Install |
|---|---|---|
| Node.js | node --version |
nodejs.org |
| jq | jq --version |
brew install jq |
| GitHub CLI | gh --version |
brew install gh |
| Git | git --version |
brew install git |
BoB Framework
Section titled “BoB Framework”Warp Drive ships with BoB at ~/.claude/: with BoB installed, /warp-drive, /stop-warp-drive, the bob warp CLI and the state machine are already there. Where the pieces live lists the files.
Add warp to your PATH
Section titled “Add warp to your PATH”Add this to your ~/.zshrc (or ~/.bashrc), then source ~/.zshrc:
export PATH="$HOME/.claude/bin:$PATH"Verify: bob warp help
Hook Registration
Section titled “Hook Registration”scripts/deploy.sh wires the warp-drive hooks into ~/.claude/settings.json: resume on session start, the commit gate, the edit tracker, the docs and commit detectors, and the Stop hook. Without them the loop still runs but loses its safety rails. Check the wiring on this machine:
node ~/.claude/scripts/warp-drive/state-machine.js declared "$(pwd)"The full hook table is in internals.
First-Time Setup
Section titled “First-Time Setup”1. Initialize your project with BoB tooling
Section titled “1. Initialize your project with BoB tooling”bob init -y /path/to/your/projectCreates the .claude/ directory. Terminal only; /automation runs it for you if you skip it.
2. Set automation level
Section titled “2. Set automation level”In Claude Code, run /automation level 2 — or launch with claude -a2. Warp-drive requires Level 2 or 3. claude -a2 trusts the directory only when the operator has a manifest for it — see when claude -aN trusts a directory.
| Level | Who It’s For | Merge Strategy |
|---|---|---|
| Level 2 (Trusted Dev) | Active development, you review PRs | Creates PR for review |
| Level 3 (Autonomous) | Trusted mode, minimal intervention | Direct merge to master |
3. Create work items
Section titled “3. Create work items”Run /requirement to create an issue with acceptance-criteria checkboxes, labelled req. Defects are bug issues (/report-bug); maintenance — a dependency bump, a CVE update, a runtime upgrade, a config migration, a cleanup — is a task filed with /task (see Task work). When it is ready for warp-drive:
gh issue edit 42 --add-label approved4. Create a feature branch (Level 2 only)
Section titled “4. Create a feature branch (Level 2 only)”Run /start-work. Level 3 auto-creates feature/issue-NN-title instead.
5. Launch
Section titled “5. Launch”/warp-drive 42 # work on issue #42/warp-drive # auto-discover next approved issueRunning Warp Drive
Section titled “Running Warp Drive”Starting a session
Section titled “Starting a session”From inside Claude Code, in your project directory:
/warp-drive # finds next issue with labels: req + approved/warp-drive 42 # works on issue #42 specifically/warp-drive --area billing # works the whole 'billing' workstreamWarp-drive checks prerequisites (automation level, branch, dev environment), runs the preflight gate, then enters the loop.
Kickoff scope: what a run is authorized to work
Section titled “Kickoff scope: what a run is authorized to work”The kickoff argument sets the run’s scope, not just its first item (#992):
/warp-drive 42(or--issue 42,43) — only those issues. The run ends when they are done; it never widens to the queue./warp-drive --area billing— the workstream’s approved queue./warp-drive— the full approved queue, until it is empty or a breaker trips./warp-drive --flightplan <N>— the ordered items of a flight plan. Wins over--issueand--area. An item gated by a human todo waits for that todo’scompletedlabel.
bob warp status shows the reach on its Scope: line. The other kickoff flags — -f, --timeout, --ceiling, --budget, --dry-run — are in Kickoff Flags; plan lifecycle, overlays and precedence are in internals.
Area batching
Section titled “Area batching”An area:<slug> label tags every issue in one workstream, so you can run the cluster as a unit (#260). /warp-drive --area billing narrows discovery to req + approved + area:billing in priority order; the full label name works too — /warp-drive --area area:billing is normalized to the bare slug, never doubled into area:area:billing (#1954). Manage areas from a terminal:
~/.claude/scripts/area-labels.sh list # existing area:* labels~/.claude/scripts/area-labels.sh issues billing # preview the cluster~/.claude/scripts/area-labels.sh ensure billing # create/refresh the labelSee the handbook’s Area labels section.
Preflight gate (#1923)
Section titled “Preflight gate (#1923)”Before any work is discovered, the engine grades this machine (bob doctor) and this project (bob ready --dry-run). A pass moves on to discovery. A failure moves the run to blocked and files one todo per cause (a failing check on this machine) naming its fix command — a machine condition in bigbrain, a project condition in the project repo. The same cause again comments on that todo rather than filing another, and a later passing gate closes it (one cause, one todo). Run the gate yourself any time with bob preflight.
Host drift is advisory (#3160). The machine is measured against the commit it was deployed from, not against bigbrain’s working tree, so a commit, an uncommitted edit or a feature-branch checkout in bigbrain never fails another project’s gate. The gate fails only when BOB_HOME is unusable: a surface that was never deployed, node / jq / gh missing, the host check erroring, or BOB_HOME differing from its recorded commit. Every other finding is an advisory: it is printed with its fix on a ~ line, it is listed in warp-preflight/1 as advisories[] and on the Preflight: line of bob warp status, and it changes neither the verdict nor the exit code. No todo is filed for an advisory.
| Advisory | Means | Fix it names |
|---|---|---|
host:behind |
the source checkout has moved since the deploy and deploy-managed files changed | scripts/deploy.sh |
host:symlinks |
a link in BOB_HOME does not resolve on this machine |
per link: scripts/deploy.sh or rm |
host:toolchain |
the forge shim is not first on PATH, or the shell guard line is missing |
scripts/bob-install.sh |
host:surfaces / host:settings_hooks |
a difference measured with no recorded commit to compare against, or after a deploy from a tree with uncommitted changes | scripts/deploy.sh |
project:labels |
the forge could not be reached, so the label set was not read | gh auth login |
project:ports |
a port collision in another project’s band | make port-migrate |
The host is graded once. bob ready reads the same host check, and the gate does not repeat its verdict: there is no project:host. A declared bob_version pin that is not met still blocks, as project:version.
When a run is blocked:
- Read the verdict —
bob warp status(thePreflight:line), ornode ~/.claude/scripts/warp-drive/state-machine.js status "$(pwd)" | jq .preflight. - Run the fix commands the todo names, then add the
completedlabel. The agent never compensates by hand. - Resume, or end the run:
Terminal window node ~/.claude/scripts/warp-drive/state-machine.js transition "$(pwd)" preflight_retrynode ~/.claude/scripts/warp-drive/state-machine.js preflight "$(pwd)" # a pass resumes where the run wasnode ~/.claude/scripts/warp-drive/state-machine.js transition "$(pwd)" blocked_end # or stop cleanly
The host check re-runs at every chunk and requirement boundary; _workflow.preflight_recheck: false turns that re-check off. The re-check never stops a project that is not BOB_SOURCE (#3160): whatever it finds is recorded on state.preflight.rechecks[] (marked blocking: false) and named in the transition’s response as PREFLIGHT RE-CHECK — ADVISORY, no todo is filed, and the next chunk starts. A commit to bigbrain used to stop unrelated running projects here. Only a run on BOB_SOURCE is stopped mid-run, and only by a hard finding. On that self-hosted run the machine is measured against the commit it was deployed from, so the run’s own commits are not drift. When no deploy is recorded the re-check falls back to the working tree and separates the run’s own work from machine drift (#1982, #2203): a deploy-managed surface or the settings.json hook wiring the branch changed since kickoff is not drift — it cannot be deployed mid-session by design (#2040), so settings_hooks is graded against the source at the kickoff commit and a match is reported as self-hosting: hooks changed on this branch (not deployed until merge) on the re-check record (state.preflight.rechecks[].notes), never as a deploy todo. Genuine machine drift — the deployed settings.json differs from the hooks the deployed commit declares — still blocks a BOB_SOURCE run with the todo. --converge, the mid-session re-check and self-hosted runs: internals.
Self-hosting redeploy at kickoff (#2472). On BOB_SOURCE the machine is usually behind source at kickoff, because the previous run merged scripts, commands or hooks. Being behind never fails the gate (#3160), but a run on BOB_SOURCE executes the deployed scripts, so bob warp start and bob tick deploy before claude starts when it is safe: when the gate names host:behind (or a host:surfaces finding) and nothing else failed, the checkout is the clean main branch and no other Claude Code session would lose its hooks, the launcher runs the source checkout’s scripts/deploy.sh, then bob doctor, then the gate again. No todo is filed and nothing respawns. Other live sessions block it only when the settings.json hooks block has drift (#2126) — the live-check rule: a converged hooks block means deploy.sh syncs surfaces and writes no settings.json, and surface rsync never silences a hook, so a run kicks off on a laptop with other sessions open. When the redeploy is not safe — a dirty tree, a feature branch, or a live session with hooks drift — the launcher prints why it did not deploy and the run starts on the deployed scripts; a failed check that a deploy does not fix blocks exactly as above. --no-converge-host (or WARP_LAUNCH_CONVERGE_HOST=0) turns it off. A consuming project is never touched. The decision lives in one place, scripts/warp-drive/self-redeploy.js (classify / converge, contract self-redeploy/1). It never passes --force-live.
Self-hosting redeploy inside a run (#2472). A run on BOB_SOURCE that merges scripts, commands or hooks is then running on a stale ~/.claude. At the merged boundary (continue_yes) the re-check reads how far source has moved since the deploy (the host:behind advisory, #3160), and it records self-hosting: N surface(s) behind (own merge). When the redeploy is safe (the checkout is the clean main branch, and either no other Claude Code session is live or the hooks block is converged — #2126), the run does not continue and does not block. It checkpoints the segment with resume_after: "deploy" and hands off through segment-spawn.sh. The fresh window deploys after this session exits, then resumes. The kickoff gate does the same when a session starts on a machine that is behind: the run hands off instead of filing a todo.
When the redeploy is not safe, the rules are:
- Only behind source: the run continues on the deployed scripts, and the refusal is noted on the re-check. Behind source never blocks (#3160).
- A hard host finding (
BOB_HOMEdiffers from the commit it records, a surface was never deployed): the run blocks with the usual todo, which now says why the redeploy was refused: dirty tree, feature branch, a main branch that is notorigin/<main>after a fetch (not-on-origin, ororigin-unverifiedwhen origin cannot be fetched — only pushed commits are ever deployed, #3629), other live sessions with hooks drift, or a checkdeploy.shdoes not fix. A live-sessions refusal names every live session and queues the deploy (#3162): BoB runs it when those sessions end, so there is nothing to deploy by hand.bob doctorshowsdeploy queued since …until it has run, and the run never passes--force-live(#2126).
next_chunk never redeploys, because it is mid-requirement on a feature branch. _workflow.self_redeploy: false switches the route off. Every redeploy is recorded on state.preflight.redeploys[].
Forge degraded mode (#1751)
Section titled “Forge degraded mode (#1751)”A GitHub outage does not end a run. Every transition reads the forge health signal that the gh shim keeps from real call outcomes. The read costs no network call. A degraded or unreachable verdict puts the run in degraded mode, recorded on state.forge:
- Coding, testing and committing carry on with the work already discovered. They never needed the forge.
- Reads of issues fall back to the read cache. A cached answer says so, and it is treated as possibly stale.
- Writes are spooled by the write queue: AC ticks, comments and the chunk report. The call exits 0 with a
[forge-queue]marker. A spooled create prints aBOB-Q…handle the run can reference. The spool replays in order on recovery. - The engine replays the spool at every item boundary (#3191). On entry to
awaiting_continueanddiscoveringit delivers what is queued, each call bounded, and says what it delivered. A queued write counts as done: the agent never waits to see one land and never replays by hand. If the forge is still failing, the replay stops at its first call and the rest waits for the next boundary._workflow.forge_replay_on_item_boundary: falseturns it off. - A rate limit is
degraded, neverunreachable.bob warp statusand the session card read the forge live, so a limit that begins while the run stands at a boundary shows at once. - Landing the work parks, except a plain
git pushon a rate limit. The integration branch and a Level 3 direct merge land bygit push, which an API rate limit cannot fail, so the run entersmergingunparked and the instruction says so. A PR or merge on green still parks. Enteringmergingwhile degraded parks the run there with the reason, instead of letting the push, PR or merge fail.bob warp statusand the session card show the phase asparked. While parked, the engine refusesmerged,merge_failedandpush_failed. When the forge fails mid-merge, the agent parks the run itself withmerge_failed --data '{"forge_park":true,"reason":"…"}'.
The run resumes on its own once the forge answers. forge-check probes the forge, and releases the park when the verdict is operational. It then prints the merging instruction the run carries out:
node ~/.claude/scripts/warp-drive/state-machine.js forge-check "$(pwd)" # exit 0 = normal or resumed · 3 = degraded or still parkedSeeing it. A degraded run never looks like a normal one. bob warp status prints a red Forge: row, and shows the phase as parked [merging] while parked. The bob warp viz session card shows a red banner (degraded, or parked and why, with the spooled writes and cached reads) and labels the merging node parked. state-machine.js status carries forge, degraded and parked for any other reader. When a run is dead (the stale banner) and the forge is degraded or writes are still queued, status also carries stale_cause, and both the banner and bob warp status add a Likely cause: line: the session most likely stopped to wait for GitHub, no work is lost, and starting the run again carries on (#3191). tests/test-warp-degraded-loop.sh drives a whole requirement through the loop with the forge API forced to fail, and asserts the run commits its work, parks, and completes on recovery. tests/test-warp-ratelimit-loop.sh drives a run through a rate-limited todo edit at awaiting_continue, and asserts the next item starts without waiting and the engine delivers the edit at the next boundary.
Any transition that observes an operational forge releases the park too, and so does a fresh /warp-drive on the checkout (the resume hook runs the check). Recovery can lag the outage: the verdict stays degraded until the failed calls leave the health window (BOB_FORGE_WINDOW, 5 minutes). Parking adds no phase and no transition: it is a sub-mode of merging, like revalidating is of planning, so the phase contract is unchanged. _workflow.forge_degraded_mode: false turns the mode off.
What you’ll see
Section titled “What you’ll see”At Level 2/3 the loop only interrupts you for project decisions — architecture, ambiguous requirements, new dependencies, scope expansion. Each chunk produces code for 1–3 acceptance criteria, a test run, a conventional commit (feat(scope): description), and a chunk report issue. An approved item with no agent-side work left (human-only ACs, a blocked label, an open todo) is skipped for the rest of the run and listed on bob warp status’s Skipped: line.
Task work
Section titled “Task work”A task is maintenance work filed with /task under the task label. It is the third work type warp-drive runs, beside req and bug, and approved tasks sit in the same queue under the same priority rules. Why it exists and where it fits: The task work type.
What it is for. Work that keeps the project healthy but adds no behaviour: bumping a dependency, applying a security advisory, moving to a new runtime before an end-of-life date, migrating a config format, removing dead code. If the change alters what a user can do, it is a req.
How its definition of done differs. A requirement is done when its acceptance criteria tick; a bug when a regression test goes red to green. A task is done when every step in its ## Verification checklist has been run and passed — the build on the new runtime, the advisory no longer reported, the deploy smoke test clean. The steps are still - [ ] checkboxes, so progress counts the same way, but the loop treats them as commands to execute, not outcomes to describe:
| Requirement | Task | |
|---|---|---|
| Definition of done | ## Acceptance Criteria ticked |
## Verification steps run and passed |
| Scope bounded by | The parent capability (Part of #NN) |
The Trigger: line — advisory, CVE, EOL date, release link |
In testing |
The project’s checks | The project’s checks plus every Verification step, exactly as written |
| A failing step | — | A failed verification (tests_failed), never a pass |
requirement_done |
Accepted | Refused while any Verification step is unchecked or the section is missing |
A task has no parent capability, and that is correct — /groom does not flag it as an orphan.
How a task session reads in bob warp status. The phases are the same ones a requirement runs; the Phase: row shows the task label with the raw phase in brackets:
WARP DRIVE ACTIVE Project: bodmail Phase: verifying (run the declared Verification steps) [testing] Issue: 57 Branch: feature/issue-57-node-22| Raw phase | Reads as |
|---|---|
coding |
task_executing (perform the maintenance change) |
testing |
verifying (run the declared Verification steps) |
requirement_complete |
task_complete |
Bug work is relabelled the same way (bug_fixing, regression_gate, bug_verified). The Verification verdict recorded at requirement_done is on state.task (node ~/.claude/scripts/warp-drive/state-machine.js status "$(pwd)" | jq .task). Engine detail: internals.
When it finishes
Section titled “When it finishes”After all chunks are done, warp-drive runs a final test, merges (L2: PR / L3: direct merge), and at Level 2 asks “Continue to next task?” — yes discovers the next approved issue, no files a session summary and exits.
On a protected main branch (#3718). When the project’s manifest declares _meta.branch_protection enforced for admins, GitHub refuses the Level 3 direct merge’s push. The run lands through a pull request instead: it pushes the branch, opens the PR and hands it to merge-on-green.sh, which fast-forwards the main branch once the required checks are green. The merging phase says so in a PROTECTED BRANCH MERGE block, and merged or merge_failed follows the script’s exit code, as on an unattended run. promotion.js status shows Protected branch: yes. See How an attended run lands on a protected branch.
Area-completion briefings (#3169). Once an item has landed (promoted / promote_skipped), and again when the session ends, the engine starts bob brief --completions --project . --apply beside the run. When the item drained its area, meaning no approved req, bug or task is left in it, that command files an area-completion briefing in the project’s repo. The briefing says what shipped, what changed, what CI showed and whether it is safe to release. The command also writes the page www/bob/briefings/project-<name>-area-<slug>.html, publishes it and sends one bob notify line. The run never waits for it. Its output is in .claude/.area-completions.log, and _workflow.area_completion_briefs: false turns it off. bob tick runs the same detection through bob brief --reconcile. See Area-completion briefings.
Launching from a shell: bob warp start (#2049)
Section titled “Launching from a shell: bob warp start (#2049)”Every launcher goes through one engine, scripts/warp-drive/launch.sh: bob warp start from any shell, bob fork fork per worktree window, and segment-spawn.sh per handoff all build and send the same line — claude -a<level> [-rdb] "/warp-drive <scope flags>" — so level, RDB and scope are parameters rather than three hand-typed strings. bob warp start [--flightplan N | --issue N | --area <slug>] [--level 1-4] [--rdb] [--session <name>] [--attach] runs the preflight gate above first (exit 1 with the verdict, nothing started), then creates the detached tmux session warp-<stem>[-<scope>] in the project root, sends the line into its one window, and prints the session name with the tmux -CC attach -t … line; it refuses (exit 3) when that session already exists. --attach opens an iTerm2 control-mode window on an interactive Mac (the same gated auto-attach segment handoffs use — macOS-only, skipped on Linux) and falls back to a plain tmux attach on a Linux TTY. bob warp status|stop|log work unchanged against a run started this way. It is not bob warp session start, which binds an integration-branch stream and launches nothing. The whole shell lifecycle — groom → plan → start → observe → stop → report — is one page: Warp Drive from the Command Line.
The warp CLI
Section titled “The warp CLI”bob warp observes, controls and — through bob warp start — launches warp-drive from outside Claude Code: docker ps for the loop. Terminal only. --help on any verb prints that verb’s usage and acts on nothing, wherever it sits in the argv.
bob warp start --flightplan <N> [--rdb] # launch a run in a tmux session (preflight first)bob warp status # phase, issue, commitsbob warp status --watch # redraw every 5s (--interval 2 to change; --log adds phase history)bob warp log # phase transition historybob warp viz # live session graph in the browser (--tailnet: serve on Tailscale)bob warp config # workflow settings for this projectbob warp stop # graceful abort (moves to aborted)bob warp stop --hard # emergency reset (deletes state file)bob warp session start <branch> [--area <slug>|--flightplan <N>] # integration-branch streambob warp session end # clear the session branch + scope bindingbob warp session status # resolved branch configbob warp finalize [--dry-run] # ship the integration branch to mainbob warp helpwarp status
Section titled “warp status”WARP DRIVE ACTIVE Project: bodmail Viz: http://farm-01.<tailnet>.ts.net:6952/warp Phase: coding Issue: 42 Branch: feature/issue-42-email-parser Level: 2 Area: email Host: warp-seg-bodmail-2 (tmux — tmux attach -t warp-seg-bodmail-2) Attach: bob warp attach bodmail (follows the run across segments) Preflight: PASS on bodmail-mac — host:converged, project:READY · re-checks: 1 Chunk: 2 / 5
Commits: 1 Chunks: 1 Tests: 3 Reports: 1A run riding out a forge outage adds a red Forge: row under Phase: — DEGRADED MODE · forge UNREACHABLE (…) since … · 2 write(s) spooled, or PARKED at merging — <reason> · resume: state-machine.js forge-check with the phase shown as parked [merging]. Once the forge is back, the row turns dim and names the episodes the run rode out. See Forge degraded mode.
If the Claude session has ended but the state file remains, it shows (stale). The Host: row gives the command to re-attach to this segment. The Attach: row is the one command that follows the run to every later segment, the same one the bob viz card shows (#3163). The Viz: row is the live /warp link while the project’s dashboard runs — the MagicDNS name for a tailnet-bound server, so it opens from a laptop as printed — and Viz: not running — bob warp viz (<bind>) otherwise, naming the bind this machine would serve on (#2639). bob warp status --json carries the same answer as viz (bob-viz-url/1: url, the {magicdns, ip, localhost} set, or start). What every row means: internals.
warp viz
Section titled “warp viz”bob warp viz opens the live session graph (/warp on the project’s dashboard server): phase topology, current phase, acceptance-criteria checklist, token burn, and gitlog / tree / feed / CI / flight-plan tabs. Its Run region also shows what is running in the project right now: workflows and their agents, background subagents, and Idle between items since HH:MM when nothing runs between items (see Working now). To watch a farm-hosted run from a laptop, serve it on the tailnet and open the printed URL:
ssh farm-01cd ~/projects/bodfeed && bob warp viz --tailnethttp://farm-01.<tailnet>.ts.net:7012/warp MagicDNS: http://farm-01.<tailnet>.ts.net:7012/warp tailnet IP: http://100.103.9.107:7012/warp (where MagicDNS does not resolve)The first line is always one URL you can open: the MagicDNS name when the dashboard is on the tailnet, otherwise localhost. Scripts can take that line. The indented, labelled lines after it list every other address the dashboard answers on (#2918). The tailnet-IP line is for a client where MagicDNS doesn’t resolve. If Tailscale reports no MagicDNS name, the first line is the IP and the MagicDNS: line says the name is missing. If the dashboard is on localhost and the machine has a tailnet address, the launcher says local only — not reachable from other machines and names the command that fixes it (bob warp viz --tailnet, or declaring dashboard.bind: tailnet for the machine, #2636). It never prints a tailnet URL that a localhost-only server would refuse. A reused server prints the same lines as a freshly started one. --json prints one bob-viz-launch/1 document instead, with bind, url and the {magicdns, ip, localhost} set (null for an address the dashboard doesn’t answer on), and opens no browser.
From a linked git worktree, the dashboard is the main checkout’s server (#4041), and its plain /warp page shows the main checkout’s run. So every URL printed from a worktree ends in ?root=<worktree root>, the page that shows the worktree’s own run (#4313). That covers the lines, url, urls and a root field in bob-viz-launch/1, and the viz: line and viz.url of bob warp start, which reads them. From the main checkout the URL has no ?root=, and root is null.
After the server is up, you don’t need to remember that URL. bob warp status prints it on its Viz: row, and bob doctor ends each running dashboard’s line with its /warp link and closes the section with the /bob fleet link (#2639).
Needs you banner (#3058). A run that stopped for Paul shows a banner at the top of its warp viz page, above every pane. That covers a run held in blocked or budget_exceeded, one whose decision timed out, and one that ended drained or skipped on a todo. The banner shows the handoff record’s plain-English line, why it matters, the command to copy and the link to the todo, in the same words as the phone push and the terminal handoff. The page polls /warp/handoff.json every 15 s. The server keeps a record only while its todo is still an open, not-completed todo, and a record without a todo only while the run is still in the phase that raised it. The banner therefore clears within one refresh of the todo getting completed. A decision still pending (not timed out) keeps the older Waiting on human strip below it. See the handoff record.
If a pane says the server can no longer run gh, run bob warp viz again from a fresh shell — it restarts the server. The bind posture, self-update and every pane: internals.
To see every project on every fleet machine at once, run bob viz (--tailnet works the same way). It opens /bob on the same server: one tile per project, each linking to that project’s bob warp viz card. See bob viz. bob viz --page briefings opens /briefings on the same server — the latest briefing and its history, the page a Telegram briefing links to (#2244); see Briefing → The page.
One address for the whole machine (bob serve, #2407). Instead of a port per project, a machine can answer on its MagicDNS name alone, with every project path-routed under it: http(s)://farm-01.<tailnet>.ts.net/bob for the fleet view and /<project>/warp for a project’s monitor. Declare a serve block on the machine’s machines.json entry (https, the fleet project behind /bob, and the projects to route), then converge it:
bob serve # report: each route ok / missing / differs / stale, and the URL it answers onbob serve --apply # add or update BoB's routes in tailscale serve; exit 0 when convergedbob serve only touches the routes it declared. A page someone serves by hand at / is listed as undeclared and left alone. It never runs tailscale funnel: the address is on the tailnet only (decision #1773). Until the tailnet has HTTPS certificates, it serves HTTP on port 80 and reports https unavailable; the next --apply after they are turned on moves the routes to HTTPS. If --apply fails with error: permission, tailscale needs the one-time sudo tailscale set --operator=$USER on that machine. If it fails with error: path-handler, the serve config already holds a file handler that is not BoB’s, such as that hand-served page, and tailscaled then wants root for every change: run the sudo tailscale serve … command the fix line prints, then bob serve --apply again. Exit 3 means Tailscale is not running there, and nothing was changed. Flags and exit codes: CLI reference.
Each route needs a server to answer it after a reboot, so bob serve --apply also installs and starts a dashboard unit for every routed project checked out on that machine. The BoB source checkout uses its own unit, and every other project gets one of its own (bob-viz-<project>.service on Linux, com.bob.viz.<project> on macOS). A unit that is stopped, missing or out of date is drift, and --apply fixes it. A running unit is left alone. A project that is not checked out on that machine is listed as not checked out and never counts as drift. bob doctor prints one serve: line on a machine that declares serve: INFO when everything is converged, WARN naming what drifted. Units: bob viz → Project units.
Once a machine declares serve, its dashboards bind to loopback (tailscaled owns the tailnet listener), and bob viz, bob warp viz, bob warp status and bob dash serve --list print the MagicDNS address first. Until bob serve --apply has run, they print the local URL first and label the MagicDNS one not live yet. The fleet row on warp viz and the bob viz tiles link to a serve machine at its MagicDNS address too.
warp stop, log, config
Section titled “warp stop, log, config”bob warp stop moves the run to aborted; the next Claude Code session in that project runs cleanup (WIP commit, abort report). bob warp stop --hard deletes the state file with no cleanup. bob warp log prints every phase transition with its event and time. bob warp config shows the automation level, RDB status and every _workflow setting.
Integration-branch streams
Section titled “Integration-branch streams”A single-issue run merges (or PRs) its requirement to main on its own. A multi-item run accumulates its items on one branch and ships them once (#268), so main’s CI runs once instead of once per item.
A multi-item run pins its own stream (#4405). When you start a run with --flightplan <N>, --area <slug> or more than one --issue, and the checkout has no _branch.session, init pins stream/<machine-id>-<YYYY-MM-DD> itself. It asks nothing. It goes through the same path as bob warp session start <branch> [--flightplan <N> | --area <slug>]: the branch is created off main (or resumed and freshened), pinned, and bound to the scope. merge_target records the stream, and the init response says so in one line beginning INTEGRATION BRANCH (#4405). A plan that declares its own branch (#1181) keeps that branch.
- At the end of the run,
session_endingnames the branch. At Level 3, when the promotion decision lets the run merge to main (directly, or through merge-on-green on a protected main), the engine runsbob warp finalizeitself atsession_ended. It reports a ship only whenorigin/<main>moved; otherwise it says main did not move and names the command. At Level 2 or 4 (wherebob warp finalizeopens a PR rather than merging), or under aprceiling, the summary puts the branch andbob warp finalizeunder “Needs you”. - A second checkout the same day gets its own stream. A branch can be checked out in only one worktree. When
stream/<machine-id>-<date>is already checked out in another worktree of the repo, the run pinsstream/<machine-id>-<date>-<worktree name>instead, so it still accumulates its items rather than merging each one to main. - A refused kickoff touches nothing. The stream is pinned after every kickoff refusal (the Level 4 cost-ceiling gate included), so a run that does not start leaves the checkout and
_branch.sessionas they were. - These runs merge per item, as before: a single-issue run, an unattended run (
_workflow.unattended, merge-on-green per item), a checkout that already has a pin, and any project with_workflow.auto_session_branch: false. - A pin set mid-run counts. On entry to
mergingthe engine re-reads_branch.session, so abob warp session startafterinitmoves the next merge to the new branch. The merge instruction says when the target changed.
To open a stream yourself, with your own branch name:
# 1. Open the stream, bound to its scope (#1177)bob warp session start integration/billing-rollout --area billing # or --flightplan <N>
# 2. Run /warp-drive inside Claude Code — it defaults to the bound scope,# and each requirement merges into the stream, not main
# 3. Inspect at any pointbob warp session status
# 4. Ship the accumulated branchbob warp finalize --dry-run # preview the exact git/gh commandsbob warp finalize # L2: one consolidated PR | L3: freshen, merge to main, push (protected main: one PR + merge-on-green, #4184)
# 5. (If finalize didn't already) clear the streambob warp session end # the git branch is left untouchedfinalize refuses mid-requirement and keeps the stream intact on any failure, so fix and retry. Freshness, guards and merge behaviour: internals.
Session segmentation (long runs)
Section titled “Session segmentation (long runs)”A multi-hour run degrades in quality as one Claude context accumulates history. Session segmentation lets a Level 3/4 run checkpoint at a clean per-requirement boundary, end the session, and resume in a fresh context — pairing naturally with an integration-branch stream.
It is on by default (segment_after_tokens: 120000, #959): 73% of spend happens past 150k context, so a run segments once a segment crosses 120k tokens. Set _workflow.segment_after_tokens: 0 to opt out.
What you’ll see: the finished session stops and a fresh claude "/warp-drive" continues in its own warp-seg-<stem>-<N> tmux session, on the same branch and scope. On macOS with iTerm2 it pops as a new window (WARP_SEG_AUTOATTACH=off disables that; approve the one-time Automation prompt). Don’t stash around a boundary: changes you left before kickoff are carried, and only in-flight work refuses — see Checkpoint dirt rules. To watch from a terminal, run bob warp attach once: it follows the run to each new segment, so there is no segment number to look up (Attach once, follow the run). bob warp status shows the thresholds and segment lineage. While a hand-off is in flight it also shows a Continuation: line: the new tmux session, the command to attach to it, and what the iTerm2 auto-attach did (auto-attach skipped: not-darwin on a headless farm, for example). The line goes away once the fresh session has taken over the run (#3142). Mechanics: internals.
Redeploy segments (#2472). When a self-hosted run hands off for a redeploy (see Preflight gate), the checkpoint carries resume_after: "deploy", and segment-spawn.sh sends the fresh warp-seg window one line instead of the bare resume:
node <bigbrain>/scripts/warp-drive/self-redeploy.js continue --project <bigbrain> --wait-pid <retiring pid> && claude -a3 "/warp-drive"It ends the retiring session and waits for its process to exit. An interactive session does not exit when the agent ends its turn, so at Level 3 and above the new window ends it for you (#3142): it finds the tmux pane that hosts the old session and sends /exit to it, 20 seconds after it starts and every 30 seconds after that, until the session is gone. If /exit is ignored for three minutes, it closes the pane, but only in a session the run created (warp-*, warp-seg-*). A pane in your own tmux session is never closed.
You are never asked to type /exit (#3163). When the new window cannot reach the old session through a tmux pane (it runs in a plain terminal outside tmux, or the run is below Level 3), or /exit was ignored in your own tmux session, the new window ends the old process itself: SIGTERM once its last turn has had time to finish, repeated, then SIGKILL if SIGTERM is ignored past the cutoff. The terminal window stays open; only Claude Code in it ends. segment-spawn.sh prints the line the old session ends with as a say: line, and the redeploy’s record on the state file (state.preflight.redeploys[]) carries how the session ended as retired_by: exit-sent, pane-closed, signalled, killed, or exited when it had already ended.
| Setting | Default | What it sets |
|---|---|---|
WARP_REDEPLOY_EXIT_GRACE_S |
20 | Seconds before the first /exit, so the old session can finish its turn |
WARP_REDEPLOY_EXIT_RESEND_S |
30 | Seconds between repeats of /exit or SIGTERM |
WARP_REDEPLOY_EXIT_KILL_S |
180 | Seconds of ignored /exit before a run-owned pane is closed (any other pane: the process is signalled), or of ignored SIGTERM before SIGKILL |
WARP_REDEPLOY_TERM_GRACE_S |
60 | Seconds before the first SIGTERM when there is no pane to send /exit to |
WARP_REDEPLOY_WAIT_S |
none | A cutoff for the whole wait. Without it the wait has no limit |
A recycled PID does not count as the old session, because the process start time must match too (#3121). Then it runs the source checkout’s scripts/deploy.sh and bob doctor, from a plain shell with no live session. Only a converged doctor lets the && start claude. A wait timeout, a failed deploy or a doctor drift prints the reason in the window, records it on state.preflight.redeploys[], and files or bumps the preflight todo. The run is left unlaunched, never started on a half-deployed ~/.claude. It never passes --force-live. segment-spawn.sh <root> --print shows the line without sending it.
Retrying a failed redeploy (#3122). Every failed continuation prints one retry command, and the preflight todo carries the same command:
node <bigbrain>/scripts/warp-drive/self-redeploy.js continue --project <bigbrain> && claude -a3 "/warp-drive"It is the same line without --wait-pid. With no PID, continue waits for nothing: it deploys, runs bob doctor, and only a converged doctor starts claude. Fix the cause first, make sure the old session has exited, then run the command. When the failure was a live-session refusal, the deploy is already queued (#3162): wait for bob doctor to stop showing deploy queued since …, then run the command to resume. Never build a retry by hand. The level and the -rdb flag in the printed command are the run’s own. --json carries the command as retry_command.
A retry completes the record of the attempt that failed. The run still counts one redeploy, and attempts on the record says how many tries it took. A continue that cannot read its arguments is a failed continuation too: it is recorded, the todo is updated, and the message names the retry command. It exits 2, so the && after it never starts claude.
After a failed redeploy, bob warp status marks an older pass as stale: Preflight: PASS (stale — checked before the redeploy that failed). The pass describes the host before the merge, so do not read it as the host’s state now.
Stopping and Resuming
Section titled “Stopping and Resuming”Stopping
Section titled “Stopping”Slash command (inside Claude Code):
/stop-warp-drive # Graceful: WIP commit, abort report, cleanup/stop-warp-drive reset # Hard: delete state file immediatelyTerminal: bob warp stop (graceful) or bob warp stop --hard (hard reset).
A graceful stop moves to aborted, commits uncommitted changes as WIP, files an abort report, and cleans up the state file. A hard reset only deletes the state file — uncommitted work stays in the working tree.
Resuming
Section titled “Resuming”Warp-drive auto-resumes: start Claude Code in a project that has .claude/.warp-drive-state.json and the SessionStart hook injects the current phase. A stale state (the original process ended) still resumes, noting the staleness.
The Stop Hook
Section titled “The Stop Hook”Exiting Claude Code (/exit, Ctrl+D) during a run is blocked while a chunk report is pending — file the report first. In any other phase exit is allowed with an advisory.
Remote Mode (RDB)
Section titled “Remote Mode (RDB)”For runs where you’re away from the terminal. Enable the Remote Decision Bridge with /rdb on (or launch with claude -rdb), or just tell Claude you’re stepping away. Decisions then go to Telegram, and every phase transition sends a notification ([warp-drive] coding -- chunk 2/5 for #42), ending with a completion notice ([warp-drive] session ending -- 4 commits, 3 chunks).
Decision timeout. If warp-drive needs your input (terminal or Telegram) and gets no answer within 10 minutes, it files a todo issue assigned to you describing the decision, skips the blocked item, and continues — or ends the session if nothing else remains.
Dev Environment Integration
Section titled “Dev Environment Integration”When a project has a dev.json, warp-drive manages the dev environment:
| Phase | Action | Script |
|---|---|---|
| prerequisites | Full dev-up: start server, migrate, seed, provision users | dev-up.sh "$(pwd)" --verbose |
| coding | Seed coverage advisory when schema changes | check-seed-coverage.sh "$(pwd)" |
| chunk_complete | Health check before the next chunk | dev-up.sh "$(pwd)" --check |
| chunk_complete (recovery) | Full dev-up if the check fails | dev-up.sh "$(pwd)" --verbose |
A dev server that can’t be recovered after 2 attempts blocks the run and escalates. To set one up, cp ~/.claude/templates/dev.json ./dev.json and customize server.command, server.port, server.health, seed and auth. Projects without dev.json skip all of this; _workflow.dev_health_check: false disables it even when one exists. Full reference: dev-lifecycle.md.
Troubleshooting
Section titled “Troubleshooting”Recovery
Section titled “Recovery”If a session is lost (crash, worktree deletion), use the disaster recovery CLI from a terminal:
~/.claude/scripts/lib/cdr.sh status # check current state~/.claude/scripts/lib/cdr.sh recover /path/to/project # re-clone, re-run cdi~/.claude/scripts/lib/cdr.sh reset /path/to/project # delete the warp-drive state fileCommit hashes are recorded in the state file (commit_hashes[]); if the worktree is lost, git fetch origin and git cherry-pick them. ~/.claude/scripts/lib/preflight-check.sh verifies jq, node, git, gh auth, hook files and core scripts.
“Automation level must be 2 or 3”
Section titled ““Automation level must be 2 or 3””Run /automation level 2 inside Claude Code first.
“Must not be on main/master”
Section titled ““Must not be on main/master””Run /start-work (Level 2), or let Level 3 auto-create the branch.
Warp-drive won’t start — “Active session exists”
Section titled “Warp-drive won’t start — “Active session exists””A previous session didn’t clean up. Run bob warp status; if it is stale, bob warp stop --hard.
Commit blocked — “only allowed during committing phase”
Section titled “Commit blocked — “only allowed during committing phase””The gate hook is working: a commit was attempted outside the committing phase, usually because a phase was skipped. Check bob warp log.
Phase stays at committing after a commit
Section titled “Phase stays at committing after a commit”The commit detector did not advance. Check git log -1, then state-machine.js status "$(pwd)" | jq .last_bash_eval.verdict — the hook records why it decided. Hooks run from ~/.claude/hooks/, so a fix merged to BOB_SOURCE is inert until deploy.sh. See Hook Registration.
A chunk has nothing to commit in this repo (#4158)
Section titled “A chunk has nothing to commit in this repo (#4158)”Some chunks finish their work somewhere else: the commit is in another repo, such as an operator-repo manifest edit and its PR, or the chunk is operational and changes no file anywhere. HEAD in the run’s repo never moves, so the commit detector has nothing to fire on, and a bare committed is refused. Leave committing by firing committed with evidence, in one of two forms:
# The commit is in another repo and pushed: name it, or its PRnode ~/.claude/scripts/warp-drive/state-machine.js transition "$(pwd)" committed --data '{"commit":"<owner>/<repo>@<sha>"}'node ~/.claude/scripts/warp-drive/state-machine.js transition "$(pwd)" committed --data '{"commit":"https://github.com/<owner>/<repo>/pull/<n>"}'
# The chunk changed nothing anywhere: say what it did insteadnode ~/.claude/scripts/warp-drive/state-machine.js transition "$(pwd)" committed --data '{"no_diff":"<one line>"}'The engine checks the evidence before it moves the phase. Both forms are accepted only when the run’s tree is clean, because both say nothing is left to commit here. BoB’s own state files and dirt that was there before the run started do not count. A commit must also exist on that repo’s remote: the engine asks GitHub for the commit, or for the PR’s head commit. It must name another repo, not this run’s own origin, because a commit here moves HEAD. A fire with no evidence, a SHA the remote does not have, a commit in this run’s own repo, or either form on a dirty tree (for example, work a commit hook rejected) is still refused, and the phase stays at committing.
An accepted fire is not a hook miss, so no harness defect is recorded. The evidence is stored on the chunk (completed_chunks[].commit_evidence). The reporting instruction marks the chunk as cross-repo or no-diff, and state-machine.js status "$(pwd)" | jq .off_tree_chunks lists these chunks for the session summary.
Can’t exit Claude Code — “report is missing”
Section titled “Can’t exit Claude Code — “report is missing””The Stop hook blocks exit after a commit with no chunk report. Have Claude file the report, or kill the terminal.
Doom loop warning
Section titled “Doom loop warning”The edit tracker saw the same file edited too many times (default 5). The approach isn’t working — try an alternative or escalate.
bob warp viz — “already running on another bind”
Section titled “bob warp viz — “already running on another bind””The project’s dashboard already holds its port on the other bind — usually an earlier bob warp viz --tailnet is still serving on the Tailscale address, so plain bob warp viz (which probes and serves on localhost) can’t start a second one. bob warp viz prints the server’s refusal and names both fixes:
bob warp viz --tailnet # reuse the running tailnet serverbob dash serve --stop <project> # or stop it, then re-run plain bob warp vizThe reverse is not a refusal: bob warp viz --tailnet (or bob viz --tailnet) over this project’s own localhost server stops it and restarts it on the Tailscale address, the same way a stale server is restarted — a localhost server can never answer on the tailnet URL, so reusing it would not help (#2917). For any other startup failure, bob warp viz prints the tail of the server’s log ($XDG_STATE_HOME/bob/dashboard-<project>.log, default ~/.local/state/bob/) — read that before retrying (#2560).
State file is corrupted
Section titled “State file is corrupted”bob warp stop --hard deletes it. Your code changes stay in the working tree.
warp command not found
Section titled “warp command not found”Add ~/.claude/bin to your PATH — see Add warp to your PATH.
Where the rest lives
Section titled “Where the rest lives”Sections that used to live on this page, and where they are now:
| Section | Now in |
|---|---|
| Phase flow, phase details, overlays, levels | internals → The state machine |
| Self-verification, doom loops, stall detection, cost budget | internals → Hooks and safety rails |
| Discovery caching, skipping blocked items | internals → Discovery caching |
| Stream freshness, guards, merging, finalize | internals → Integration-branch streams |
| Checkpoint dirt, macOS surfacing | internals → Session segmentation |
| Per-area re-validation | internals → Per-Area Re-Validation |
| Configuration, gates, reasoning budget | internals → Configuration |
| What gets created | internals → What Gets Created |
| State file, CLI, transition table | Warp-Drive State Reference |
| Every state-machine flag | Warp-drive state-machine flags (generated) |