Appearance
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.
TL;DR
Two ways — but they do different jobs. The slash command runs the loop (it needs a live Claude Code session); the terminal 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):
bash
warp status # check on the loop
warp stop # abort from outsideWhere 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. Each requirement is a GitHub Issue with checkboxes; warp-drive works through them one commit at a time.
Installation
Prerequisites
These must be installed on your machine:
| 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
Warp Drive is part of BoB (Big ol' Brain), the global Claude Code tooling framework at ~/.claude/. If you have BoB installed, warp-drive is already available. The key files:
~/.claude/
├── bin/warp # CLI (this is new)
├── commands/warp-drive.md # /warp-drive slash command
├── commands/dev-up.md # /dev-up slash command
├── skills/stop-warp-drive/SKILL.md # /stop-warp-drive skill
├── scripts/warp-drive/state-machine.js # State machine engine
├── scripts/dev-lifecycle/ # Dev environment IaC scripts
│ ├── dev-up.sh # Full lifecycle orchestrator
│ ├── health-check.sh # Lightweight health probe
│ ├── provision-users.sh # Test user provisioning
│ └── check-seed-coverage.sh # Seed data coverage gate
├── hooks/warp-drive-gate.sh # Blocks commits outside committing phase
├── hooks/warp-drive-inject.sh # Resumes state on session start
├── hooks/warp-drive-edit-tracker.sh # Doom loop detection
├── hooks/warp-drive-pre-exit.sh # Self-verification before phase advance
├── hooks/warp-drive-stop.sh # Blocks exit when report is pending
├── templates/dev.json # Dev manifest template
├── templates/seed/ # Seed data templates
└── docs/warp-drive.md # This fileAdd warp to your PATH
Add this to your ~/.zshrc (or ~/.bashrc):
bash
# BoB CLI tools
export PATH="$HOME/.claude/bin:$PATH"Then reload: source ~/.zshrc
Verify: warp help
Hook Registration
The warp-drive hooks are registered in ~/.claude/settings.json automatically. If you're setting up from scratch, the hooks need entries in:
| Hook Type | File | Purpose |
|---|---|---|
| SessionStart | warp-drive-inject.sh | Resume from last phase |
| PreToolUse (Bash) | warp-drive-gate.sh | Block commits outside committing phase |
| PreToolUse (Bash) | warp-drive-pre-exit.sh | Self-verification before phase advance |
| PostToolUse (Edit|Write) | warp-drive-edit-tracker.sh | Track edits, detect doom loops |
| PostToolUse (Edit|Write|Bash) | warp-drive-docs-detector.sh | Auto-detect the AC tick (gh issue edit <req> --body…) or a doc edit → advance state (#1852) |
| PostToolUse (Bash) | warp-drive-commit-detector.sh | Auto-detect git commit → advance state |
| Stop | warp-drive-stop.sh | Block exit when report is pending |
Every auto-advance and gate in this table is a declared behaviour with a stable fingerprint — see Declared automatic behaviours; state-machine.js declared "$(pwd)" reports whether each one is present and wired on this machine.
If hooks aren't registered, warp-drive still works — you just lose the safety rails (commits could happen at the wrong time, doom loops won't be caught, auto-detection won't fire, etc.).
If the phase stays at committing after a successful commit, the hook did not fire. Hooks run from the deployed copy in ~/.claude/hooks/, so a fix merged to BOB_SOURCE is inert until scripts/deploy.sh syncs it; the detector also refuses to advance while HEAD still equals the sha recorded at tests_passed (a commit a commit-msg hook rejected). Check git log -1 first, then state-machine.js status. Firing transition … committed by hand is a recovery step, not the normal path — until #1830 every commit needed it because the hook gated on a payload field the harness never sends; that is fixed and covered by tests/test-commit-gate-detect.sh against the real payload shape.
First-Time Setup
1. Initialize your project with BoB tooling
Terminal (Terminal only — cdi is a shell alias; there is no slash form, but /automation will run it for you if you skip it):
bash
cdi -y /path/to/your/projectThis creates the .claude/ directory structure in your project. If you skip this, /automation will run it for you.
2. Set automation level
Inside Claude Code, in your project directory:
Slash command:
/automation level 2Terminal (the -a2 startup flag on the claude shell wrapper sets the same level when you launch a session):
bash
claude -a2This writes permission rules to .claude/settings.local.json. Warp-drive requires Level 2 or 3.
| 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
Create a requirement as a GitHub Issue:
Slash command (Slash command only — authoring the issue needs the session's conversation context):
/requirementThis creates an issue with acceptance criteria checkboxes, labeled req. When ready for warp-drive, add the approved label (plain gh, from any shell):
bash
gh issue edit 42 --add-label approved4. Create a feature branch (Level 2 only)
Slash command (Slash command only — no terminal entrypoint; Level 3 auto-creates the branch instead):
/start-workLevel 3 auto-creates branches. Level 2 requires you to start one first.
5. Launch
Slash command (Slash command only — the loop runs in the Claude Code session; the terminal warp only observes/controls it):
/warp-drive 42 # work on issue #42
/warp-drive # auto-discover next approved issueRunning Warp Drive
Starting a session
From inside Claude Code, in your project directory:
Slash command (Slash command only — runs in-session; the terminal warp observes/controls but never starts the loop):
/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' workstream (see below)Warp-drive checks prerequisites (automation level, branch, test command), then enters the loop.
Kickoff scope: what a run is authorized to work
The kickoff argument defines the run's scope, not just its first work item (#992):
/warp-drive 42(or--issue 42,43) authorizes only the listed issue(s). When they are done, the run ends cleanly — it does not discover the next approved issue, and it does not segment into a fresh self-spawned session. Completed issues are recorded insession.completed_issues, so even a resumed segment of a multi-issue scoped run works only what remains./warp-drive --area billingauthorizes the workstream's approved queue./warp-drive(unscoped) authorizes the full approved queue — the run continues until the queue is empty or a breaker trips./warp-drive --flightplan <N>authorizes the ordered items of a persisted flight-plan issue — it consumes them in order, never widens, and ticks each off as its requirement completes. This is the most explicit scope and wins over--issueand--area. A plan may contain gating human-action todos (#1088):flightplan.js remainingreturns only runnable work, so a req gated by a pending todo is skipped, not failed — the run proceeds to non-gated items and the gated req re-enters once the todo carries the canonicalcompletedlabel (a todo closed withoutcompletednever releases; the gated work is moot). Released todos are reconcile-ticked in the plan issue automatically. Ordered plans stop at the gate instead of skipping it (#1415): when the gated item is labelledserial-only, it is a barrier — every later item iswithheldtoo, so a run can never execute downstream of a human checkpoint it has not cleared. Withheld items name the barrier holding them rather than a todo of their own, and clearing that one gate frees the chain down to the next barrier in a single step. A plan with noserial-onlyitems keeps skip semantics; a<!-- flightplan:gate-mode barrier|skip -->marker in the plan body forces either mode. Ifremainingcomes back empty whilestatusstill showsgated,withheld, ormootitems, the run ends cleanly — report gated work as waiting on its named todos, withheld work as blocked behind its barrier, andmootitems as cancelled, in the session summary rather than as failures.
Run-boundary plan maintenance (#1667). A flightplan-scoped run maintains its plan's lifecycle at both natural boundaries, using the #1666 engine — never a re-implementation. Kickoff: init runs flightplan.js lifecycle <N> before consuming the plan. stale (scope drifted since build) is refused with the exact build refresh command at L1/L2, and auto-refreshed via the same idempotent build path at L3+ (logged as plan_lifecycle in the init response); drained is refused with its gate(s) named — parity with a fully-gated remaining result; exhausted is refused as already-complete and closed via the guarded reconcile path. The gate fails open if the engine or gh is unreachable. Session end: the run converges the plan it consumed with flightplan.js reconcile --apply --plan <N> — an exhausted plan is closed with the engine's dated final summary (a plan with an unchecked open item is never closed), a drained plan is stamped with its gates named and left open, a stale plan is reported with its refresh command. Both boundaries are conditional on session.kickoff.flightplan — unscoped, --issue, and --area runs are untouched.
Flight-plan provision overlay (#1133). A flightplan-scoped run can carry the plan's registry items for the duration of the session without editing the project's base manifest. At prerequisites the run applies a plan-scoped overlay — cdprov overlay apply --plan <N> derives the items from the plan's issues via scripts/orchestrator/plan-scope.js (labels, area:* labels, stack signals in title/body) and links them, recording state at .claude/.provision-overlay.json. While the overlay is active, cdprov refresh keeps its links (union with the manifest) and cdprov status surfaces it first. At session_ending the run restores the base manifest with cdprov overlay restore (overlay-only links removed; items also in the manifest stay). Crash safety: an interrupted run leaves the state file behind — the next session's prerequisites (or any cdprov status) detects it, and cdprov overlay restore recovers it; restore is idempotent. Both transitions post a comment on the plan issue, so the lifecycle is visible from GitHub.
A flight plan wins over an issue list, which wins over --area, when more than one is given. The scope rule lives in one place (kickoffScope() in scripts/warp-drive/kickoff.js) and is enforced at every point that could otherwise widen a run: the segmentation decision computes "more work" within scope, the discovering phase refuses to fall back to the queue on a scoped run, and the resume context handed to a fresh segment names the scope explicitly. warp status shows the reach on its Scope: line. See the Kickoff Flags reference.
Area batching
Within a single repo, issues for distinct workstreams — a module, a feature, a capability rollout — are intermixed. An area:<slug> label tags every issue in one workstream (a capability owns the slug; its child requirements inherit it), so you can warp-drive the cluster as a unit instead of hand-picking issue numbers (#260).
Slash command:
/warp-drive --area billingnarrows discovery to req + approved + area:billing and works those requirements in priority order, one per cycle. Manage and inspect areas with the helper script (Terminal only — these label helpers have no slash form):
Terminal:
bash
~/.claude/scripts/area-labels.sh list # existing area:* labels
~/.claude/scripts/area-labels.sh issues billing # preview the cluster (status overview)
~/.claude/scripts/area-labels.sh ensure billing # idempotently create/refresh the labelSee the handbook's Area labels section for the full convention.
Preflight gate (#1923)
/warp-drive used to start coding on whatever the machine happened to have: an unwired or stale hook (#1856, #1897, #1913) meant the agent reached a phase whose auto-advance never fired and compensated by hand. The #1833 family reports that after the fact; the preflight gate prevents it. It is a real phase — prerequisites → preflight → discovering | blocked — and the decision is made in the state machine, never in the prompt.
What it runs. state-machine.js preflight <root> [--converge] calls the engine scripts/warp-drive/host-preflight.js (also bob preflight), which reuses two existing verdicts verbatim:
| Check | Engine | Asks |
|---|---|---|
host | bob doctor (scripts/checks/host-drift.js, #1921) | Is this machine's BOB_HOME what deploy.sh would produce — surfaces, symlinks, toolchain, settings hook wiring? |
project | bob ready --dry-run (scripts/ready/ready.js, #1922) | Is this project warp-drive-ready — .claude/ initialised, manifest links materialised, labels, port band, dev.json/checks.json valid? |
Non-mutating by default. --converge runs the real bob ready (every step is idempotent) first, then re-runs both checks — the fresh re-check decides, never the convergence run's own optimism. The result is a warp-preflight/1 document: per-check verdict, every failure with its remediation command, a fingerprint over the failing set, and the todo it filed.
Pass. The engine fires preflight_ok → discovering. The verdict — machine id, per-check verdicts, timestamps — is recorded at state.preflight, shown by warp status (Preflight: line) and state-machine.js status (.preflight), and reported in the session summary.
Fail. The engine fires preflight_failed → blocked, after filing one todo (todo,warp-drive,p2-high, assigned to the caller) in the project repo whose body conforms to the todo format contract: a summary, one numbered step per failed check with its exact fix command, a closing re-check step, and a Blocked by #NN link to the in-scope issue. An open todo carrying the same fingerprint marker (<!-- warp-preflight: … -->) is bumped with a comment, never refiled — repeat runs of the same drift converge on one issue. RDB, when enabled, gets the [warp-drive] BLOCKED notification.
From blocked the coding loop is unreachable: the only exits are preflight_retry (back to preflight — run the gate again after the human acted; a pass resumes exactly where the run was) and blocked_end (→ session_ending, the summary reports the verdict). preflight_ok / preflight_failed are engine-fired only: a hand-fired transition … preflight_ok is refused with the command to run the gate. An engine that cannot run — a missing script, an unparseable answer — is verdict error and fails closed, never a pass.
Mid-session re-check. Drift does not only happen before a run. At next_chunk (every chunk boundary) and continue_yes (every requirement boundary, which is also where a resumed segment re-enters) the state machine re-runs the cheap host section (~150 ms). New drift blocks the next chunk exactly the way a dev-health failure does — the transition is refused, the run moves to blocked, the todo is filed/bumped, and preflight_retry → preflight_ok returns it to the interrupted phase. Each re-check is recorded under state.preflight.rechecks. _workflow.preflight_recheck: false opts out of the re-check (the kickoff gate itself has no opt-out).
cdfork fork --from-issues runs the same gate once, before fanning out, so N worktree sessions never start on a machine one session would refuse.
Discovery caching
Warp-drive used to re-derive the ordered approved queue from scratch every cycle. Discovery is now fingerprint-cached (#1069): each cycle, scripts/warp-drive/discovery-cache.js queue makes one cheap gh issue list call and hashes the actionable set — the approved open req/bug issue numbers plus each one's priority label and serial-only flag (the attributes that determine queue order). That 12-char hash is the invalidation signal:
- Unchanged fingerprint → the cached ordered queue (stored under
discovery_cachein.claude/.warp-drive-state.json) is reused as-is; no re-derivation, no extra discovery passes. On a multi-chunk run where nothing material changes, discovery after the first cycle is ~free. - Changed fingerprint — an issue was closed, opened, re-prioritized, newly
approved, or hadserial-onlyflipped — → the queue is re-derived throughdiscover-queue.sh(still the single ordering source, #517) and the cache refreshes. A newly-approved p1 that appears mid-run therefore invalidates immediately and is picked up exactly as before; the cache never trades freshness for tokens. Title edits and other order-irrelevant changes deliberately do not invalidate.
The cache is in-run session state only: it lives inside the warp-drive state file and is deleted with it at session end — never a persisted PM file. It applies to --area and full-queue runs; --issue and --flightplan runs have a fixed scope and never consult the live queue (the cache composes with a flight plan but is not required by it). Inspect or drop it with discovery-cache.js status|invalidate --cwd <project-root>.
Skipping blocked items (#1270)
An approved item can turn out to have zero agent-side work remaining — every open AC is human-only, it carries the blocked label, or it is gated by an open todo (Blocked by #NN). Two mechanisms keep such items from wedging a run:
Queue exclusion (before selection). Blocked work never surfaces as runnable in the first place:
discover-queue.shenforces the whole membership rule —approved, thereq/bugtype filter, and theblockedexclusion — in one shared jq expression, so every consumer inherits it identically regardless of how the rows were fetched (#1794). Passing--label approvedtoghis a server-side narrowing optimisation on top of that, never the gate itself.discover-queue.sh(and everything built on it — warp-drive discovery,/what-next, the discovery cache) excludesblocked-labelled issues from the runnable queue. Removing the label restores the issue; a label flip also changes the discovery-cache fingerprint, so a cached queue can never serve a newly-blocked item.flightplan.js remainingexcludes plan items gated by any open todo — in-plan todo rows and off-plan todos whose body names the item in aBlocked by #NNline. The standardcompleteddone-signal releases the exclusion (an open todo labelledcompletedimposes no gate; one closed withcompletedleaves the open list). A gatedserial-onlyitem additionally acts as a barrier: the run order is truncated there and every later item is withheld (#1415) — see flightplan.md.
The skip path (after selection). If a blocked item still reaches planning (e.g. it was blocked after the queue was fetched, or the blockage is only visible in the issue body), the loop fires work_skipped instead of aborting: the item is recorded to state.skipped_issues as {issue, reason} and the run returns straight to discovering for the next item — state-only, with no commit and no prerequisites re-run. Skipped issues are excluded from re-discovery for the remainder of the run, so the loop can never re-select the same blocked item in a loop.
Skips are surfaced everywhere the run reports: warp status shows a Skipped: line (issue + reason, omitted when none), and the session summary includes a "Skipped (blocked)" section. The skip exit exists only at planning — blockage discovered mid-coding (after edits exist) stays on the error-escalation/todo path, which defines cleanup.
What you'll see
Once running, warp-drive works through phases automatically. At Level 2/3, it only interrupts you for project decisions — architecture questions, ambiguous requirements, new dependencies, or scope expansion. Everything else is autonomous.
Each chunk produces:
- Code changes implementing 1-3 acceptance criteria
- A test run
- A conventional commit (
feat(scope): description) - A chunk report filed as a GitHub Issue
When it finishes
After all chunks are done, warp-drive:
- Runs a final test
- Merges (L2: creates PR / L3: direct merge to master)
- Asks "Continue to next task?"
- If yes → discovers next approved issue
- If no → files a session summary and exits
The warp CLI
The warp command is a terminal tool for observing and controlling warp-drive from outside Claude Code. Think of it as docker ps for the autonomous loop. It is Terminal only (there is no slash equivalent) — and it observes/controls a running loop rather than starting one; use /warp-drive to start.
Commands
Terminal:
bash
warp status # What phase is it in? What issue? How many commits?
warp status --log # ...plus the phase-history log below (works with --watch too)
warp log # Full phase transition history
warp viz # Open the live session graph page in the browser (alias: warp status --web)
warp viz --tailnet # ...serve it on this machine's Tailscale address (watch a farm run from a laptop)
warp config # Show workflow settings for this project
warp stop # Graceful abort (moves to aborted phase)
warp stop --hard # Emergency reset (deletes state file)
warp session start <branch> [--area <slug>|--flightplan <N>] # Start an integration-branch stream, optionally scope-bound (see below)
warp session end # Clear the session branch + scope binding
warp session status # Resolved branch config
warp finalize [--dry-run] # Ship the integration branch to main
warp help # Help textwarp status
Shows the live state of the loop:
WARP DRIVE ACTIVE
Project: bodmail
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)
Preflight: PASS on bodmail-mac — host:converged, project:READY · re-checks: 1
Chunk: 2 / 5
Commits: 1 Chunks: 1 Tests: 3 Reports: 1
Started: 2026-02-18T14:30:00Z
PID: 12345If the Claude session has ended but the state file remains, it shows (stale).
The Host row (#1742). An active run always says where it lives, so a blank spot can no longer mean either "not tmux-hosted" or "the lookup failed". The resolution order is: the tmux session recorded at kickoff → tmux resolved from the run's PID by walking its process ancestry (covers sessions predating the recording) → the iTerm window recorded at kickoff (iTerm · <profile>) → detached when none of those is known. Where the host can be re-entered, the row shows the command that does it (tmux attach -t <session>).
The Preflight row (#1923). The preflight gate's last verdict for this run: PASS / FAIL / ERROR, the machine it graded, each check's verdict, the todo it filed or bumped when blocked (todo #77 (bumped)), the number of mid-session host re-checks ((last FAILED) when the latest one blocked) and, after a mid-session block, the phase a retry resumes at. It reads not yet run until the gate has decided — the same .preflight object that state-machine.js status emits.
The same resolution backs the warp viz status card — both read scripts/warp-drive/host-resolve.js, so the CLI and the page cannot disagree about where a run is. An idle project has no host row at all, and an active run whose resolver cannot be reached reads unknown (host resolver unavailable) rather than going silent.
warp viz
warp viz opens the live session graph page (/warp on the project's dashboard server) — the engine's phase topology as an SVG, with the traversed path highlighted and the current phase pulsing, streaming over SSE.
Watching a farm-hosted run from a laptop (--tailnet, #1736). A run on the farm needs a monitor you can open from wherever you are. warp viz --tailnet (equivalently cds --tailnet, or DASH_BIND=tailnet in a service definition) serves the monitor on that machine's Tailscale address instead of loopback and prints the resulting URL — which is the whole interface when you are on the far end of an SSH session:
bash
ssh farm-01
cd ~/projects/bodfeed && warp viz --tailnet
# http://farm-01.tail3bfead.ts.net:6152/warp <- open this from the laptopNo port forwarding, no public DNS, no reverse proxy: the tailnet is the transport, exactly as it already is for the cross-machine audit (#415). Per-project ports are unchanged — the URL's port is still the project's +2 band slot from the ledger, so several projects can be served at once on the same box.
The bind is deliberately narrow. Tailnet mode binds only the tailnet address (loopback is not bound in that mode, and neither is the LAN or public interface — on a farm box with a routable public IP, that address answers nothing). The resolved address must fall inside Tailscale's CGNAT range or the server refuses to start, and Tailscale being down is likewise a refusal naming its reason — never a fallback to a wider bind. The monitor is unauthenticated and shells out to git/gh on the host, which is why it is never exposed further than the tailnet; the full reasoning, and what would have to change to revisit it, is in Project Monitor Exposure Posture.
Deploy-fresh server reuse (#1603). A running dashboard server is reused only when it is running the currently deployed code. The server exposes a deploy stamp at /warp/server.json (the mtime of the script the process loaded); before reuse, warp viz runs scripts/dashboard/server-fresh.sh <port> [host] against it and restarts the server when the deployed file is newer than the stamp — or when the stamp endpoint 404s on an otherwise-alive server (a pre-stamp process is stale by definition). Without this, a long-running server would keep serving pre-deploy routes and templates indefinitely (Node caches modules at process start — the same pitfall as the launchpad :7777 server).
Live self-update (#1734). The launch-time probe alone still left the monitor a command you re-run: an already-open page had no way to learn the server underneath it changed. Both halves now watch continuously. The server stats the on-disk script it loaded (15s tick, DASH_SELF_WATCH_MS override) and, when a deploy rewrites it, marks its stamp stale, waits for a second stable mtime reading (a mid-write deploy never launches a half-copied script), then restarts in place — SSE clients are ended so their retry: hint reconnects them to the successor spawned on the same port. The page captures the deploy stamp at load and re-probes it every 15s plus on every SSE reconnect; a changed stamp reloads the page (?root= rides the URL; theme, view, and active tab persist in localStorage). Anti-thrash: the page holds off while the stamp reports stale (reloading then would refetch the old code) and caps auto-reloads at one per 30s, so a flapping stamp can never loop. The utility tabs are generated from a single PANES registry — each row declares its endpoint, renderer, and (optionally) a relevant(st) predicate evaluated on every state push — so a pane that becomes relevant mid-session (a flight-plan kickoff, the CI tab to come) appears without a reload, and the next pane is one registry row rather than edits scattered per feature.
The page has two views, switched by the view: iso/flat header button (persisted in localStorage, per browser):
- iso (default) — the layered phase DAG projected onto a 2.5D isometric staircase, using both screen axes so the whole graph fits a typical viewport (1440×900) with no scrolling. Loop-back edges arc through the open area above the staircase; traversal counts, visited/current highlighting, the side lane, and the stale grayscale treatment all behave exactly as in flat view. The view scales to fit the window down to a legibility floor (0.6×); a graph too large even for that keeps the floor and scrolls inside the graph pane instead of shrinking further or clipping.
- flat — the original vertical spine layout; taller than a screen for the full engine topology, but with strictly top-to-bottom reading order.
Both layouts are pure functions of /warp/graph.json — live state colors the picture but never moves nodes, so the shape is stable across sessions.
Fleet links (#1744) — navigation, not aggregation. The sidebar's This project elsewhere section lists the fleet's machines (from machines.json, the existing registry) and links each one to this project's monitor there, using the machine's tailnet address and the project's declared port band. The current machine is marked and not linked.
It is a row of hyperlinks and nothing more. Nothing is probed to build it, so it makes no liveness claim — no status dot, no phase, no as-of stamp — and a machine that is down or off-tailnet simply fails when you click it, at no cost per refresh. Where the port ledger has no entry for the project, the row says the port is unknown rather than composing a plausible URL. A single-machine fleet, or an unreadable registry, hides the section entirely. Rendering remote sessions (#1737) is a different problem — it must not present stale remote state as live — and is deliberately not what this row does.
Host row on the status card (#1742). The card's host row answers the same question warp status does, from the same resolver: the tmux session, the recorded iTerm window, or detached, labelled with the kind, plus an attach row carrying the command when the host can be re-entered. See warp status for the resolution order.
Work row on the status card (#1825). The card's work row is the issue reference alone — #906, linked to the issue — and asserts no work type. The type it used to print came from work_type, which the status payload defaults to req whenever the loop did not detect one, so a bug run read work req #906 with nothing in the UI marking the word as a default rather than a fact. The type is still shown where it is genuinely known: the issue carries its own bug/req label, and a real bug run gets bug phase names on the graph and in the card's phase row.
Phase tooltips (#1740). Node labels are the engine's raw phase names, so hovering a node explains it: what happens in that phase, what advances it, how long the session has spent there, and a link into that phase's row in Phase Details (new tab). The same nodes are reachable by keyboard — Tab to focus, Escape to dismiss — and the tooltip flips and clamps to stay inside the viewport in both views. In bug mode it names the overlay label and the underlying engine phase, and revalidating is explained rather than silently substituted (see Phase overlays). The copy is served with the topology on /warp/graph.json (phase_meta, sourced from PHASE_META beside the engine's PHASES), never duplicated into the page: a phase the engine adds without metadata falls back to the plain name-only tooltip rather than showing something wrong.
Session switcher (#1587). On a cdfork fan-out, a header dropdown appears whenever more than one session is detected (via /warp/sessions.json, the same scanner warp status uses), listing each as name — phase; picking one flips the graph by navigating the existing ?root=<worktree> parameter, and the adjacent all sessions link opens the /warp/sessions fleet index. If the session you're viewing ends or its worktree disappears, the page falls back to the primary session automatically. With a single session the header is unchanged — no switcher chrome.
Attention badge (#1586). When the run is waiting on a human — the budget_exceeded checkpoint at any level, or the Level-2 "continue?" prompt — a backgrounded viz tab signals it: the favicon switches to an amber alert variant, the title gains a (!) prefix, and the banner names the reason with a link to the requirement issue. All of it derives from the read-only attention block in the engine's status payload, arriving over the existing SSE stream (no polling). The notify: header button opts in to a desktop notification on the transition into the waiting state — the browser's Notification permission is requested only when you click the button, never on page load.
Token burn-down (#1585). The sidebar's Token burn section shows the session's token spend at a glance: the current spend total, and — when a budget policy applies (a kickoff --budget/--max-tokens ceiling or _workflow.max_session_tokens/max_session_usd) — the remaining budget as an absolute figure and percent, with a usage bar that shifts to amber past 75% used and red past 90%. Below the figures, a compact inline-SVG sparkline traces cumulative spend across the session's chunk snapshots, updating live with the rest of the page. The data is the existing token-snapshot machinery, delivered as a budget block inside token_usage in /warp/state.json / the SSE payload and resolved through the same budget-policy.js helper the cost breaker enforces — the display and the enforcement ceiling can never disagree. The section hides itself when the session has no snapshot data yet.
When a req is active, the sidebar shows the issue's acceptance criteria as a live checklist with a done/total summary — mirrored from the GitHub issue body (/warp/acs.json) and refreshed as warp-drive checks items off, so the page doubles as a run progress bar. Each line links to the req issue; the section hides itself when no req is active, the issue has no checkboxes, or gh is unavailable (#1584).
The page header is titled per project — the project name alone, since the page is the project's monitor and the warp session is one card inside it (a ?root=<worktree> view is titled by that worktree's project; #1733) — and issue references in the session sidebar, gitlog subjects, and feed entries are hyperlinks to the GitHub issue; commit hashes link to the commit on GitHub. Links resolve from the repo's origin remote — a repo with no remote shows plain text instead (#1583).
Utility pane (#1583, #1602). A gitlog/tree/feed/ci (plus, on flightplan-scoped runs, flightplan) pane whose presentation follows the view mode. In iso view it is a collapsible overlay at the lower-left of the graph area; its collapsed/expanded state and last-active tab persist in localStorage like the theme and view toggles. In flat view it instead docks as a full-height first column to the left of the graph (the graph shifts right to make room) and is always expanded — the persisted collapse state is neither applied nor overwritten while docked, so iso's preference survives flat↔iso round-trips, and the active tab carries across view switches (#1602). The pane is hard-capped at a third of the viewport (max-width: 33vw) in both modes, and a long gitlog subject or feed title scrolls horizontally within its own row instead of wrapping or widening the pane — message length never dictates pane width. The pane's inner edge is grabbable (col-resize cursor on hover): dragging resizes it between a 180px floor and the 33vw cap, the chosen width persists for the browser session, and dragging to/near the minimum collapses the pane in the floating (iso) mode — docked stays always-expanded per #1602 and simply clamps at the floor (#1627). The tabs, each backed by a read-only server endpoint that honors ?root=:
gitlog (
/warp/gitlog.json) — recent commits: short hash, subject, relative age; refreshed by light polling while the pane is open.tree (
/warp/difftree.json) — the uncommitted working set (staged + unstaged + untracked) as directory groups with per-file+adds/-delscounts (additions green, deletions red); when the tree is clean it shows the HEAD commit's files, labelled as such. When the repo has linked git worktrees (a cdfork fan-out), the tab adds a section per worktree — branch + path, then that worktree's changed files via the same per-root diff — so the whole fan-out's working state is visible from one page; a worktree whose diff can't be read (pruned/locked) degrades to an inline unavailable note for its own section only, and a repo with no linked worktrees renders exactly as before (#1628). The base checkout is a section too (#1741): its files sit under the same header treatment, showing its branch, a· currentmarker naming it as the page's own root, and the root path when there are other sections to tell it apart from — so a fan-out reads as one list of labelled sections rather than anonymous files followed by named worktrees. The branch is resolved server-side from the resolved root, so a?root=-scoped page names that worktree's branch; a detached checkout reads as its short SHA rather than the literalHEAD. A clean tree with no linked worktrees still renders the plainno changesempty state.feed (
/warp/issues.json) — recently updated GitHub issues (number, title, labels, relative age), newest first. The feed degrades to a quiet empty state whenghor the network is unavailable — the endpoints never write viagh.ci (
/warp/ci.json, #1735) — the repo's delivery pipeline: recent workflow runs with status glyph, workflow · branch · event, duration, start age, and a runner-locus badge (runner name such asfarm-01, orhostedderived from the labels), so the CI-locus rollout is observable from the monitor. The current branch's run is highlighted; a failing run names its failed job/step inline and links out to the run; a queued run whose requested self-hosted labels no online runner carries is flagged loud (⚠ queued — no online runner) — the silent-stall condition. Strictly read-only. API budget: onegh run listper 30s while the pane is open, jobs enrichment capped per cycle and memoized permanently for completed runs, runner roster every 120s — steady-state ≈ 2 requests/min per project, so a monitor left open all day stays far under the REST limit. Degraded states are distinct and readable (gh missing / unauthenticated / rate-limited); the tab hides only for a project with no CI workflows, via the #1734 pane registry. If CD lands (#1725 option D), deploy rows join this pane as a second section.flightplan (
/warp/flightplan.json, #1644) — visible only when the session is flightplan-scoped (session.kickoff.flightplan; the tab hides itself otherwise, falling back to gitlog if it was active). Renders the plan's ordered item checklist in the same style as the status card's AC checklist — ☑/☐, item number + title, each row linked to its issue, headed byflight-plan #N — done/total. The server mergesflightplan.js itemswith theremaining --alllifecycle classification (#1666): held items (gated/withheld/waiting/moot, plus humantodogate rows) render dimmed with a state tag, so a plan stalled on a human gate is visibly different from one with plain unworked items. Plan data that can't be derived (gh/network down) degrades to an inline notice, same convention as the feed — the payload answers{plan: N, unavailable: true}, never a well-formed0/0checklist, so "the plan is empty" and "we could not read the plan" stay distinguishable (#1780). That guard lives in the derivation itself (scripts/dashboard/flightplan-view.js), which treats any non-arrayitemsas unreadable, so every failure path reports one degraded shape.Done-ness comes from live state, not the checkbox (#1757). An item that has landed and closed on GitHub but whose plan checkbox is not ticked yet appears in no
remainingbucket — the gate resolver drops closed items by design — so it used to fall through to therunnabledefault: the tab invited you to work something already merged, and the counter read0/Nfor a whole run. The payload now also carriesflightplan.js lifecycle'suntickedClosed, and such a row renderslanded: ticked, counted indone/total, and tagged, with the header naming the lag (4 not yet ticked in the plan).landedis kept distinct fromdoneon purpose — the plan body genuinely is stale at that moment, and the tag disappearing is how you see reconcile catch up.Ticking itself no longer depends on the agent remembering: the transition into
awaiting_continueruns the #1068 check-off for the finished requirement (_workflow.flightplan_tick_on_item_boundary, default on). It is idempotent, and best-effort — aghfailure is reported in the transition output rather than blocking the loop, and thesession_endingreconcile still sweeps whatever it missed.
If a tab's endpoint fails before it has rendered anything (404 from a stale server, network error), the pane replaces the loading… placeholder with an explicit "data unavailable" note naming the failure and the likely fix (restart via warp viz); once a tab has rendered real data, a later failure keeps the last good content instead (#1604).
The Area: line appears only when the session is area-scoped (/warp-drive --area <slug>); an unscoped run omits it (#613). The Scope: line shows the run's authorized reach (#992): issues #42, #43 (1 done, 1 remaining) for an issue-scoped run, flight-plan #N for a flightplan-scoped one, full approved queue for an unscoped one (an area-scoped run's reach is the Area: line). A scope inherited from a stream binding (#1177) shows the same way. The warp viz status card carries the same reach, split across two rows (#1644, #1825): a flightplan row linking #N to the plan issue for a flight-plan run, and a scope row worded exactly as the Scope: line for an issue-scoped (issues #42, #43 (1 done, 1 remaining)) or unscoped (full approved queue) run. Each row is omitted entirely — no placeholder — when it has nothing to say, so an area-scoped run shows neither (its reach is the card's area row) and a flight-plan run is never described twice.
The Tmux: line names the tmux session hosting the run, so you can find where it lives — invaluable across a cdfork fan-out where several sessions run at once (#990). It is recorded at kickoff when warp-drive starts inside tmux (the declarative source of truth); for sessions that predate this it is best-effort resolved by matching the recorded PID against tmux pane ancestry. It is omitted entirely when the run is not tmux-hosted (same omit-when-empty convention as Area:). The warp viz status card shows the same tmux row, resolved in the same order (recorded kickoff context first, PID-ancestry fallback) via the page's state.json/SSE payload, and likewise omitted when the run is not tmux-hosted (#1628). When more than one active warp-drive session is detected on the machine (e.g. cdfork worktrees), warp status lists every session — project, branch, phase, and tmux name — so a single warp status surfaces the whole fan-out, not just the current project's run.
Live/watch mode (#612). Pass --watch (aliases: --live, -w) to re-render the status on a fixed interval instead of printing once — handy parked in a tmux/iTerm split:
warp status --watch # refresh every 5s (default)
warp status --watch --interval 2 # refresh every 2s
warp status -w # short alias
warp status --watch --log # also show the phase-history log below--interval <seconds> tunes the cadence (default 5s; non-numeric input falls back to the default, sub-1s clamps to the 1s floor). The view redraws in place (no scroll spam) and exits cleanly when the session ends, goes stale, or is aborted — leaving a one-line summary on screen. With no active session it prints the usual "No active session" message and exits. Ctrl-C exits cleanly and restores the cursor.
--log (#1003). Pass --log to append the phase-history log (the same table warp log prints) beneath the standard session info. It works on both the one-shot warp status and the live --watch view — in watch mode the log re-renders each tick, so new transitions appear in place as they happen. When the session has no recorded history the log section is omitted entirely (no empty header).
warp stop
From outside Claude Code, warp stop transitions the state machine to the aborted phase. The next time Claude Code starts in that project, it picks up the abort and runs cleanup (WIP commit, abort report).
warp stop --hard deletes the state file immediately. No cleanup, no report. Use this when the state file is corrupted or you just want a clean slate.
warp log
Shows every phase transition with timestamps:
Phase History (bodmail)
# Phase Event Time
── ──────────────────────── ─────────────────── ─────────────────────
0 prerequisites init 2026-02-18T14:30:00Z
1 discovering prerequisites_ok 2026-02-18T14:30:05Z
2 planning work_selected 2026-02-18T14:30:12Z
...warp config
Shows the current workflow tuning for the project — automation level, RDB status, and all _workflow settings with their defaults.
Integration-branch streams
By default warp-drive merges (or PRs) each requirement to main on its own. For a cluster of related requirements — a capability rollout, a multi-part feature — you often want to accumulate them on one branch and ship once. That's an integration-branch stream (capability #268).
When to use it
- The requirements are tightly coupled (shared files, a staged feature) and you'd rather review/ship them as a unit than as N separate merges.
- You want main to stay clean until the whole stream is green, with a single consolidated PR (L2) or one rebase+merge (L3) at the end.
Use a plain per-requirement merge (the default with no session branch) for independent issues.
The workflow
The stream controls are Terminal only (warp session / warp finalize have no slash form); the loop itself is still started with /warp-drive inside the session, as noted in step 2.
Terminal:
bash
# 1. Open the stream — creates the branch off main and records it as _branch.session.
# Bind the stream's scope at the same time (#1177) so kickoffs line up automatically:
warp session start integration/billing-rollout --area billing # or --flightplan <N>
# 2. Run the loop over the cluster (each requirement merges into the session branch, not main)
# On a bound stream a plain /warp-drive defaults to the bound scope — no flags needed.
# An explicitly different --area/--flightplan is refused; --issue <N> is allowed (narrower).
# 3. Inspect at any point
warp session status # main / session / current / scope binding / merge target & strategy
# 4. Ship the accumulated branch in one shot
warp finalize --dry-run # preview the exact git/gh commands
warp finalize # L2: one consolidated PR --base main | L3: freshen (merge main in), merge to main, push
# 5. (If finalize didn't already) clear the stream
warp session end # removes _branch.session + the scope binding; the git branch is left untouchedStart-of-work freshness (#1178)
A stream is freshened from main at every start-of-work seam — warp-drive kickoff (prerequisites), each segment resume, and warp session start on an existing branch — via the shared scripts/warp-drive/stream-freshness.sh. The sync merges main into the stream (never rebases — the stream is pushed; direction fixed by the #1179 policy): a single fetch + ancestry check makes the up-to-date case a fast no-op, a behind stream absorbs main routinely instead of all at once at finalize, and clean syncs are recorded in the run's phase history (warp status --log shows stream_sync(synced)). A conflicting sync is a blockable event: the merge is aborted with the stream untouched, and the loop files a todo naming the conflicted files and stops — the standard completed-label contract resumes the stream once a human resolves. From the warp session start CLI the same conflict refuses the start with the files named.
Wrong-branch guard (#1180)
The freshness pass also ends on the stream branch: if a binding is active but HEAD is elsewhere (main, a stale child branch, detached HEAD), stream-freshness.sh checks the stream out ("checked_out": true in its JSON) so a kickoff never silently works off-stream. A dirty working tree that makes git refuse that checkout is a distinct blockable signal — status blocked, exit 4, HEAD and local changes untouched — and the loop files a todo / prompts per level rather than continuing on the wrong branch. On the enforcement side, hooks/session-branch-guard.sh (active whenever _branch.session is set) additionally refuses git commit on main or on any branch that is neither the stream nor a child of the stream; per-requirement child branches — which merge back into the stream via the LOCAL accumulate path — remain allowed, and the refusal message names the bound stream. check-branch.sh (the edit-on-master guard) is unchanged.
Stream↔scope binding (#1177)
warp session start <branch> --area <slug> / --flightplan <N> records the scope binding as _branch.scope alongside the branch pin (per-checkout, in settings.local.json) — see the branch-config reference for the schema and enforcement rules. In short: an unscoped /warp-drive on a bound stream defaults to the bound scope (so unrelated queue work can never land on the stream branch), a conflicting explicit scope is refused with the override paths named, and the binding survives session end, segmentation, and restarts until warp session end / warp finalize clear it with the pin. Because it is per-checkout, cdfork worktrees each carry their own binding — parallel streams on different areas coexist.
How merging changes
When a session branch is set, merge_target != main, and _workflow.session_merge is local (the default), the merging phase accumulates locally: it merges each requirement's child branch into the session branch per merge_strategy, pushes the session branch, deletes the child branch, and never touches main / opens no PR. This applies at both Level 2 and Level 3.
Set _workflow.session_merge to pr to keep the legacy behavior (a PR or direct-to-main merge per requirement) even with a session branch. With no session branch set, behavior is unchanged regardless of the value.
session_merge | session branch set | merging behavior |
|---|---|---|
local (default) | yes | merge child → session, push session, delete child; main untouched |
pr | yes | per-requirement PR (L2) / direct-to-main (L3) |
| any | no | unchanged legacy behavior |
finalize safety
warp finalize refuses to run when there is no integration branch or while a warp-drive session is mid-stream (it only proceeds with no active state or at awaiting_continue/completed, so it never ships a half-done requirement). On a conflict, push, or auth failure it aborts cleanly and leaves _branch.session (and any _branch.scope binding) intact so you can fix and retry. --dry-run prints the exact commands without executing them.
Unified freshness/finalize policy (#1179)
Finalize and start-of-work freshness are one coherent policy: merge-in, never rebase. The L3 finalize path freshens the stream through the same stream-freshness.sh engine (a rebase would replay the stream's absorbed sync-merge commits into mangled history), then ships to main with the configured _branch.merge_strategy flag — ff-only (default) works because a freshened stream strictly descends from main, and preserves the sync merges in history; configure merge-commit for an explicit --no-ff ship commit. Rebase applies nowhere in warp tooling — it is acceptable only manually on a never-pushed branch. Full policy: branch-config reference.
Session segmentation (long runs)
A multi-hour autonomous run degrades in quality as one Claude context accumulates hours of 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 — so quality stays high across an arbitrarily long queue. It pairs naturally with an integration-branch stream: each segment keeps accumulating requirements onto the same session branch, and you warp finalize once at the end.
It reuses the substrate that already exists — the persisted state file, the warp-drive-inject.sh resume hook, and the cdfork-style fresh-process spawn. There is no new resume engine.
When to use it
It is on by default (segment_after_tokens: 120000, #959): usage analysis showed 73% of spend happens at >150k context, and segmentation-at-requirement-boundary is the loop's equivalent of "/clear when switching tasks". Short sessions are unaffected in practice — a run that never crosses 120k tokens in a segment never segments. Set an explicit _workflow.segment_after_tokens: 0 to opt out.
How it works
At awaiting_continue (a requirement is done, merged onto the integration branch, git clean) a Level 3+ run consults the segmentation decision before continuing:
- Threshold — if the tokens or chunks accrued since the last checkpoint cross
_workflow.segment_after_tokens/segment_after_chunks, the run segments; otherwise it continues in-process exactly as before. Seams lower the bar (#960): when the next queued item belongs to a different cap (Part of #NN) orarea:label than the one just finished, the lowersegment_at_seam_tokensbar (default 40000) applies instead — warm context is worth less across a semantic boundary. A dimension only counts when known on both sides; missing metadata never seams, and an--area-scoped run has no area seams by construction (cap seams within the area still can). Seam-triggered segments are markedkind: "seam"in the decision and checkpoint reason. - Checkpoint —
state-machine.js checkpointrecords the boundary on the state file (appendssession.segments[], anchors the per-segment counters) without changing phase or deleting state. It requires a boundary that is clean of work — see What a checkpoint counts as dirty. - Fresh spawn —
segment-spawn.shlaunches a freshclaude -a<level> "/warp-drive"in its own per-segmentwarp-seg-<repo>-<N>tmux session (mirroring cdfork). The current session stops; the prior segment's now-idle session is reaped at the next handoff. - Resume — the fresh session's SessionStart inject hook reads the still-present state file, sees the segment lineage, and continues the same queue on the same branch. Nothing is re-passed on the command line — all kickoff options live in the persisted state, so no state is lost.
Segmentation only fires when more work remains within the run's kickoff scope (#992): an issue-scoped run whose issue(s) are done reports moreWork: false and routes to a clean session end instead of handing the approved queue to a fresh self-spawned session — the incident that motivated the scope rule.
The trigger measures per-segment usage (delta since the last checkpoint), never whole-run cumulative — otherwise the persisted state would re-trip the threshold immediately in the fresh session and loop forever. warp status surfaces the thresholds, the per-segment usage so far, and the checkpoint lineage under segmentation. See the state reference for the fields and CLI actions.
What a checkpoint counts as dirty (#1756)
A --flightplan run applies a plan-scoped provision overlay at prerequisites and only restores it at session_ending. Applying it rewrites the cdprov-managed ignore block in the tracked .gitignore, so the tree carries a diff for the entire window in which segmentation can fire. A checkpoint that simply asked "is the tree clean?" therefore refused every time — segmentation was effectively off for precisely the long runs it exists for.
The checkpoint classifies each dirty path instead:
| What it found | What happens |
|---|---|
| The overlay's own ignore-block edit | Tolerated — this is bookkeeping, not work, and the checkpoint proceeds. |
| Changes that pre-date the session | Refused (kind: "preexisting") — the run cannot commit your changes for you. Commit or stash them outside the run, then let it retry. |
| Anything else | Refused (kind: "inflight") — the requirement is not committed yet. Unchanged from before. |
The two refusals are separated because the action you take is completely different, and the old shared message could not tell you which one you were looking at. When both are present the message reports the in-flight work first — that is the half the run itself owns — and lists the pre-existing paths under also_preexisting.
Overlay tolerance is deliberately narrow. It applies only when an overlay is genuinely active and the working .gitignore matches HEAD exactly once the managed block is stripped from both. So a hand edit elsewhere in .gitignore still refuses, and a run with no overlay is completely unaffected — there is nothing to excuse.
If you hit the preexisting refusal mid-run, the fix is entirely outside warp-drive: git stash push -u -- <the named paths>, let the run finish, then git stash pop. You no longer need the older workaround of restoring and re-applying the overlay around the checkpoint.
segment_after_tokens defaults to 120000 (enabled, #959); segment_after_chunks defaults to 0. The default is resolved in one place (the state-machine config layer), and an explicit _workflow value — including 0 to disable — always wins over it.
Auto-surfacing the continuation on macOS (#996, #1022, #1023)
Every re-segment hands the queue to a fresh claude process, and it always surfaces as a new window — never a split of the pane you're watching (#1022 reversed the earlier pane behavior):
- A fresh per-segment session, one window each — segment-spawn creates the segment's own
warp-seg-<repo>-<N>session (N= the segment it hosts) as a single-window session, the same path whether or not warp-drive is running inside tmux. It never splits the spawning pane. Attached via iTerm2-CC, the auto-attach below pops this as a fresh native iTerm2 window near your work. - A new window every segment (#1023) — because each segment gets its own session, and a brand-new session is always unattached, the auto-attach fires on every handoff — not just the first. (The earlier shared
warp-seg-<repo>session, reused across segments, meant once a-CCclient was attached to it later continuations surfaced as tabs within that window; per-segment sessions pop a distinct window each time.) Thewarp-seg-prefix is unchanged, sowarp reap/warp status/_reap_seg_sessionkeep matching.
The fresh segment is then focused (select-pane + select-window by unique id) so an attached client lands on the running segment, never a stale same-named window from an earlier segment.
Because a per-segment session is always unattached, on macOS with iTerm2 running segment-spawn opens an iTerm2 window in control mode (tmux -CC attach -t <session>) so the continuation actually pops on screen — every time. Control mode (not a plain attach) is used deliberately — a plain attach would contend for terminal size against any other client. The first use triggers a one-time macOS Automation permission prompt (iTerm2 controlling itself via osascript); approve it once. iTerm2 is detected via TERM_PROGRAM/AppleScript is running (#1016 — the old pgrep check never matched on macOS).
-CC tab semantics: each tmux window shows as a native iTerm2 tab within its control-mode window. Closing a tab kills that tmux window — to leave the run alive, detach the control-mode client instead (tmux detach), don't close individual tabs. See iTerm2 tmux -CC.
The surfacing is strictly best-effort and safe by construction:
- Fresh session, always unattached: each per-segment
warp-seg-<repo>-<N>session is brand-new, so a new iTerm2 control-mode window is opened on every handoff — the intended behavior (a distinct window per segment). Thealready-attachedgate (which the shared session tripped for later continuations) no longer applies to continuations, only to a redundant re-spawn onto the same session. - Prior segment reaped at handoff: once the fresh segment is live, its resume-time inject reaps the previous segment's pane by id — which drops that segment's one-window session, so idle
warp-seg-*windows don't accumulate across a long run. The reap is guarded towarp-seg-*sessions only, so your own launch window (the first handoff's "previous" pane) is never closed out from under you. - Non-GUI / headless: silently skipped on non-macOS, over SSH, or when iTerm2 isn't running. The new tmux session still exists (it's just tmux); farm/headless machines are otherwise unaffected.
- Opt-out: set
WARP_SEG_AUTOATTACH=offto disable the iTerm2 window (the tmux session is still created).
If the osascript path fails for any reason, it's non-fatal — the printed attach: tmux attach -t <session> hint line still stands and the spawn still succeeds.
How It Works
The State Machine
Warp-drive is a phase-based state machine backed by a persistent JSON file at .claude/.warp-drive-state.json in your project. Hooks enforce phase ordering — commits are blocked outside the committing phase, session exit is blocked without a report.
Phase Flow
prerequisites → discovering → planning → chunking
↓
coding → updating_docs → testing → committing → reporting
↑ ↓
└──────────── next chunk ←──── chunk_complete ─────┘
↓ (all done)
requirement_complete → qa → merging
│ ↑ ↓
bugs found ───┘ └─ pass/skip
(file + fix in-session,
loop back to coding)
awaiting_continue
↙ ↘
discovering session_ending
(next task) (done)
─── Circuit-breaker checkpoint ──────────────────────────────
any phase ──(phase_timeout | ┌─ budget_continue → interrupted phase
retry_exceeded | │
coding_cycles_exceeded | │ (resume work where it halted;
no_progress | │ falls back to coding)
qa_cycles_exceeded | │
total_chunks_exceeded | │
session_timeout)──→ budget_exceeded ─┤
│
└─ budget_abort → aborted
(give up & clean up)
─────────────────────────────────────────────────────────────The state machine routes any phase into budget_exceeded when an enforced budget trips (see Configuration for the trigger reasons). From budget_exceeded, a budget_continue event resumes at the exact phase the breaker interrupted (captured in budgets.exceeded_from_phase — #1183; falls back to coding for older state files), and each approval extends the chunk cap by 10 (#1149); budget_abort moves to aborted.
The total_chunks_exceeded breaker gates starting new chunk work only (next_chunk, chunks_defined, and the like): wrap-up transitions that land or close work already in flight (merge_ready, qa_passed/qa_skipped, merged and its retries, promoted/promote_skipped/promote_failed, continue_no) are exempt (#1183), mirroring the qa-cap rule — so a session at the cap can always merge, promote, and end cleanly. The cost/token and session-time ceilings are not exempted; they halt everything. The aborted phase has its own cleanup events: abort_resolved (cleanup OK → completed), abort_cleanup_failed (cleanup failed → completed anyway), and restart (back to idle).
Phase Details
| Phase | What Happens | Advances When |
|---|---|---|
| idle | No run in flight — a state file exists but nothing is being worked | start (kickoff) |
| prerequisites | Checks automation level (2+), branch (not main), test command; runs the baseline-docs check (greenfield README/runbook/CLAUDE.md gap → unapproved req, non-blocking) | All checks pass |
| preflight | The engine grades this machine (bob doctor, #1921) and this project (bob ready --dry-run, #1922) via state-machine.js preflight — non-mutating; --converge applies the idempotent bob ready steps first, then re-checks. A failure files (or bumps, by fingerprint) one todo naming every failed check and its fix. preflight_ok / preflight_failed are engine-fired only — a hand-fired transition is refused (see Preflight gate) | Engine decides: pass → discovering, fail → blocked |
| blocked | Held on a failed preflight (at kickoff, or a mid-session host re-check at next_chunk / continue_yes). The coding loop is unreachable; nothing is compensated by hand — the todo carries the remediation | preflight_retry re-runs the gate (a pass resumes the interrupted phase); blocked_end ends the run cleanly |
| discovering | Finds work via the fingerprint-cached queue (discovery-cache.js queue → discover-queue.sh); an unchanged actionable set reuses the cached order (see Discovery caching) | Issue selected |
| planning | Reads issue, drafts approach if complex (>3 ACs or architecture decisions); a blocked item (no agent-side work) is skipped state-only via work_skipped → discovering (see Skipping blocked items). Entered across a cap/area seam it runs as the revalidating sub-mode first (see Per-Area Re-Validation) | Plan ready (or item skipped) |
| chunking | Splits work into commit-sized pieces (max 3 ACs each) | Chunks defined |
| coding | Implements the current chunk's acceptance criteria | Code complete |
| updating_docs | Checks off completed ACs in the GitHub Issue | Hook auto-detects |
| testing | Runs project test suite; retries up to 3 alternatives on failure | Tests pass |
| committing | Creates conventional commit; hook auto-detects | Commit created |
| doc_drift_check (opt-in) | Runs /doc-sync (mechanical fixes) and /doc-audit --fail-on-drift after the commit. Active when _workflow.doc_drift_gate=true. | drift_clean → reporting / drift_blocked → coding |
| reporting | Files chunk report as GitHub Issue; adds an "Iteration trail" section when the chunk took >1 verify attempt (#589) | Report filed |
| chunk_complete | Checks if more chunks remain | Next chunk or done |
| requirement_complete | Final test, close issue via PR | Merge ready |
| qa (#868) | Runs Playwright E2E (webapp-testing) against the dev-up app; records a passing run as "fully functioning" evidence before promotion. Bugs found are filed as bug issues and fixed in-session (reproduce → regression test → fix), then QA re-runs. Clean no-op skip when there is no dev/E2E surface. Bounded by max_qa_cycles (default 3) → budget_exceeded. | qa_passed/qa_skipped → merging; qa_bugs_found → coding |
| merging | L2: push + PR / L3: rebase + push master | Merged |
| promoting | Deploys to the project's promotion ceiling through the shared gate (promotion.js decide → deploy.sh); an external/pr ceiling is a clean no-op | promoted/promote_skipped → awaiting_continue; promote_failed retries |
| awaiting_continue | Asks: "Continue to next task?" | User responds |
| session_ending | Files session summary, notifies, cleans up state | Done |
| budget_exceeded | Mandatory human checkpoint reached by a tripped breaker (cost, tokens, chunk cap, QA cycles, no-progress stall) — see Cost Budget | budget_continue resumes at the interrupted phase; budget_abort → aborted |
| aborted | Run aborted; cleanup runs from here. Reachable from any phase | abort_resolved/abort_cleanup_failed → completed; restart → idle |
| completed | Terminal — the run is over and the state file no longer drives a loop | Nothing; a new kickoff starts a fresh run |
Phase overlays
Two overlays relabel a node in place rather than adding a phase, so the topology stays one graph:
- Bug work — when the selected item carries the
buglabel the loop reuses the same phases, relabelled:codingshows asbug_fixing,testingasregression_gate,requirement_completeasbug_verified. The semantics that come with the relabel are the bug contract — reproduce the failure first, root-cause it, and land a regression test that failed pre-fix and passes post-fix. The tooltip names both the overlay label and the underlying phase. revalidating—planningentered across a cap/area seam runs the freshness re-check first (see Per-Area Re-Validation) and shows asrevalidatinguntil the pass is recorded.
Automation Levels in Practice
| Behavior | Level 2 | Level 3 |
|---|---|---|
| Branch creation | You create via /start-work | Auto-creates feature/issue-NN-title |
| Merge strategy | Creates PR for review | Direct merge to master |
| Decisions | Asks for architecture, scope, ambiguity | Same |
| Continue prompt | "Continue to next task?" | Same |
| File operations | Auto-approved | Auto-approved |
| Destructive git | Blocked (force push, reset --hard) | Blocked (force push, reset --hard) |
Self-Verification
Before advancing past key phases (code_complete, tests_passed, requirement_done), a hook injects a verification prompt forcing Claude to re-read the original acceptance criteria and confirm each one is genuinely satisfied — not just that code exists.
Doom Loop Detection
A PostToolUse hook tracks how many times each file is edited during a phase. If any file exceeds the threshold (default: 5 edits), a warning is injected. This catches infinite edit cycles where Claude keeps tweaking the same file without making progress.
No-Progress / Stall Detection
Distinct from the raw retry caps, the state machine watches for stagnation on the coding ↔ testing and coding ↔ doc_drift_check loops (#267). On every tests_failed / drift_blocked it computes two signatures:
- a diff signature — a hash of
git diff HEADplus the untracked-file list (captures whether any code changed since the last attempt), and - a failure signature — a hash of the short failure string passed via
--data '{"failure":"…"}'(the failing test name or first error line).
A per-chunk stall counter increments only when both are unchanged since the previous attempt — i.e. the same failure with no new diff. Any change to either (different error, or the diff moved) is treated as legitimate slow progress and resets the counter to zero. This is the key distinction the raw coding_cycles count can't make: it halts on stagnation, not on attempts.
When the counter reaches max_no_progress (default 3), an enforced no_progress budget issue trips the circuit breaker into budget_exceeded. At Level 3 this is a mandatory human checkpoint — the loop will not spin on an unsolvable chunk burning budget; it stops and asks. budget_continue clears the guard and resumes; budget_abort cleans up. The counter also resets whenever a new chunk begins or the loop makes real headway (tests_passed, drift_clean).
Always pass the failure signature on tests_failed/drift_blocked; without it the detector falls back to the diff signature alone (still catches "no code changed", just less precisely).
Cost Budget
The circuit breaker also enforces a spend ceiling (#587). Token usage is tracked per chunk and per session; checkBudgets() compares the cumulative session total against max_session_tokens and, when crossed, emits an enforced cost_budget_exceeded issue that routes to budget_exceeded — the same mandatory Level-3 checkpoint as the other hard limits. This closes the gap where a runaway loop could burn budget while staying under every count-based cap.
- Token count is the primary signal. The check reads
token_usage.session_totalwhen captured, else the running sum ofchunk_snapshots, so it trips mid-session on accumulated spend rather than only at session end. max_session_usdis optional/secondary — an estimated-dollar ceiling via token-report's shared cost model. Prefer the token ceiling; the dollar estimate is best-effort.- Disabled by default. Both ceilings default to
0(off), so existing sessions are unaffected unless you configure them under_workflowinsettings.local.json. - The
budget_exceededcheckpoint reports actual spend vs ceiling (e.g.cost_budget_exceeded: 1500 tokens exceeded 1000 tokens) and offers the usualbudget_continue(extend) /budget_abortchoice.
Stopping and Resuming
Stopping
Two ways to stop — both reach the same state machine.
Slash command (from inside Claude Code):
/stop-warp-drive # Graceful: WIP commit, abort report, cleanup
/stop-warp-drive reset # Hard: delete state file immediatelyTerminal (from any shell):
bash
warp stop # Graceful abort
warp stop --hard # Hard resetGraceful stop does:
- Transitions to
abortedphase - Creates a WIP commit for uncommitted changes
- Files an abort report as a GitHub Issue
- Cleans up the state file
Hard reset does:
- Deletes the state file
- That's it — uncommitted work stays in the working tree
Resuming
Warp-drive auto-resumes. When you start a new Claude Code session in a project that has a .warp-drive-state.json, the SessionStart hook injects the current phase context and Claude picks up where it left off.
If the state is stale (the original Claude process has ended), the session still resumes — it just notes the staleness.
The Stop Hook
When you try to exit Claude Code (/exit, Ctrl+D, etc.) during an active warp-drive session:
- Report pending? Exit is blocked. File the chunk report first.
- Other active phase? Exit is allowed with an advisory: "Consider running session end protocol before stopping."
Remote Mode (RDB)
For autonomous sessions where you're away from the terminal.
Setup
Slash command:
/rdb onTerminal (the -rdb startup flag on the claude shell wrapper enables it when you launch a session):
bash
claude -rdbThis enables the Remote Decision Bridge. All decisions go to Telegram instead of the terminal, and every phase transition sends a notification.
What you get on Telegram
- Phase transition notifications:
[warp-drive] coding -- chunk 2/5 for #42 - Decision prompts when Claude needs input
- Completion notice:
[warp-drive] session ending -- 4 commits, 3 chunks
Decision Timeout
If warp-drive needs your input (terminal or Telegram) and you don't respond within 10 minutes:
- It files a GitHub Issue labeled
todo, assigned to you, describing the decision needed - Skips the blocked item and continues to the next chunk
- If nothing else to do, transitions to session ending
You'll find the TODO in your issue list when you're back.
Going AFK
Tell Claude you're stepping away — it treats that as /rdb on:
"I'm going to grab lunch, keep going"
Dev Environment Integration
When a project has a dev.json at its root, warp-drive automatically manages the dev environment throughout the coding loop.
What happens
| Phase | Action | Script |
|---|---|---|
| prerequisites | Full dev-up: start server, run migrations, seed data, provision users | dev-up.sh "$(pwd)" --verbose |
| coding | Seed coverage check (advisory — warns if schema changed without seed data) | check-seed-coverage.sh "$(pwd)" |
| chunk_complete | Fast health check before next chunk | dev-up.sh "$(pwd)" --check |
| chunk_complete (recovery) | Full dev-up if health check fails | dev-up.sh "$(pwd)" --verbose |
Dev health as a blocker
If the dev server goes down during coding and can't be recovered after 2 attempts, warp-drive treats it as a blockable event and escalates (see Recovery).
Setting up dev.json
Terminal (Terminal only — copying the template is a plain shell step; once dev.json exists, /dev-up in-session or dev-up from a terminal runs the lifecycle):
bash
# Copy template and customize
cp ~/.claude/templates/dev.json ./dev.json
vi dev.jsonThe template has sensible defaults for SvelteKit + Cloudflare Workers projects. Customize the server.command, server.port, server.health, seed, and auth sections for your stack.
Seed data in the loop
Warp-drive encourages additive seed data: when a chunk adds new database entities or lifecycle states, the check-seed-coverage.sh gate advises adding seed data in the same commit. This ensures dev is always testable with realistic data across all entity states.
Skipping dev lifecycle
Projects without dev.json skip all dev lifecycle steps — the integration is fully opt-in. Set _workflow.dev_health_check: false to disable even when dev.json exists.
See dev-lifecycle.md for the full IaC dev lifecycle reference.
Per-Area Re-Validation (#1008)
The approved label is stamped by /groom against a snapshot of the repo — and a long multi-area run invalidates that premise by landing commits before later areas are reached. By area 3, areas 1–2 have moved the ground the approval stood on: an AC may already be satisfied by an earlier area's commit, made redundant by just-added tooling, or now contradict landed code. When _workflow.revalidate_on_area_entry is on (the default), the loop closes this gap with a lightweight freshness re-check on area entry instead of trusting the stamp blindly.
When it fires. At work_selected, the state machine compares the just-finished item's cap/area metadata against the newly selected item's — the same seamBetween signal seam-aware segmentation uses (#960). Only a crossing (different cap or different area: label, known on both sides) arms the sub-mode; the first item of a run never re-validates (it is freshly groomed), missing metadata never seams, and a same-area selection proceeds untouched. While armed, planning displays as revalidating in warp status.
The movement gate. The pass starts deterministic and cheap — scripts/warp-drive/revalidate.js gate <issue> reads the <!-- approved-at: <short-sha> --> marker /groom stamps into the issue body and compares it against HEAD:
- No commits since the marker — the approval is fresh: the pass records a logged near-no-op (
record --no-drift) and planning proceeds with zero further overhead. - Commits landed since (or the marker is missing/unverifiable — issues approved before the marker convention are treated as movement-unknown, failure-safe): the gate reports the commit count and a changed-file triage list, and the lightweight re-check runs.
The re-check. The loop triages the changed files first — movement that doesn't touch the item's area is still a no-drift outcome. Each genuinely drifted AC is classified by the reversibility engine (#878, decision-engine.js classify):
- Two-way (reversible) — e.g. an AC already satisfied by a landed commit, or one made redundant: auto-adjusted (checkbox flip plus a one-line note comment naming the satisfying commit) and recorded.
- One-way (irreversible) — a scope change, an AC that now contradicts landed code, a new cross-area dependency: surfaced, never auto-walked — a decision checkpoint at Level 2/3; under Level 4 the decision-timeout policy (#875) applies (conforming human-only TODO, defer the item, or stop cleanly if it blocks everything).
This is a freshness re-check, not a second grooming authority: it never rewrites issue bodies wholesale, never re-derives the cluster, and /groom remains the single upstream source of truth for cluster coherence (Prime Directive). It also composes with kickoff scope (#992): it re-validates only the already-selected item — it never adds, reorders, or widens work.
Observability. record writes each pass into session state (state.revalidation — armed boundary, entries, last verdict) and pushes a phase-history line, so warp status shows the last pass and warp log / warp status --log render the summary, e.g. revalidating -- area billing: 1 auto-adjusted, 0 blocker(s). The state dies with the session — no PM files.
Configuration
All settings live in .claude/settings.local.json under _workflow. Edit directly or view with warp config.
| Setting | Default | Purpose |
|---|---|---|
max_acs_per_commit | 3 | Max acceptance criteria per chunk |
test_before_commit | true | Run tests before each commit |
auto_merge | true | L3: auto-merge to master |
max_phase_minutes | 30 | Timeout per phase before budget warning |
max_coding_cycles | 3 | Max code/test retries per chunk |
max_retries_per_chunk | 5 | Max total retries before escalation |
max_no_progress | 3 | No-progress stall halt: consecutive identical failed attempts (same failure, no new diff) before routing to budget_exceeded. See No-Progress / Stall Detection |
max_auto_decisions | 10 | L4 decision budget (#876): max auto-decisions per session before a mandatory human digest checkpoint at budget_exceeded (never auto-continued past). 0 disables it. Only bites at Level 4 — below L4 the engine never auto-decides. Surfaced in warp status / warp config. See the reversibility decision engine. |
max_edits_per_file | 5 | Doom loop detection threshold |
pre_exit_verification | true | Self-check before phase advance |
decision_timeout_minutes | 10 | Minutes before filing TODO issue. At Level 4, governs the decision-timeout policy one-way-door wait; 0 = never wait (work around or stop) |
heartbeat_on_commit | true | RDB notify on each commit |
reconcile_per_chunk | true | Auto-correct progress counters |
flightplan_tick_on_item_boundary | true | Tick the finished requirement off the run's flight plan on entering awaiting_continue, rather than trusting the prose check-off (#1757); idempotent, best-effort |
harness_defect_filing | true | On entering chunk_complete and session_ending, file unreported harness_defects[] outward as de-duplicated harness-defect bug reports (#1836); best-effort, reported in the transition output as harness_defect_filing, never blocks |
harness_defect_escalate_after | 3 | Recurrence threshold (#1838): after filing, any bigbrain harness-defect issue whose Recurrences: count reaches this is bumped to p2-high with a comment saying why (idempotent — marker comment + existing p1/p2 skip). 0 disables. Project-owned defects are never escalated |
dev_health_check | true | Run dev-up health check between chunks |
dev_seed_check | true | Run seed coverage advisory during coding |
max_total_chunks | 20 | Circuit breaker: max chunks per session (hard limit) |
max_session_minutes | 480 | Circuit breaker: max session duration in minutes (hard limit) |
max_session_tokens | 0 (disabled) | Circuit breaker: max cumulative session tokens before routing to budget_exceeded. 0 disables the check, so existing sessions are unaffected unless configured. Token count is the reliable spend signal; trips mid-session on the accumulated chunk total. See Cost Budget. |
max_session_usd | 0 (disabled) | Circuit breaker: optional/secondary estimated-dollar ceiling (via token-report's cost model). 0 disables it. Use max_session_tokens as the primary signal. |
segment_after_tokens | 120000 (enabled) | Session segmentation (L3+): checkpoint + resume in a fresh context once this many tokens accrue since the last checkpoint (per-segment, not cumulative). Default ON since #959 — 120k keeps segments comfortably below the ~150k high-cost context regime. Set an explicit 0 to disable. Soft threshold — hands work to a fresh session, does not halt. See Session segmentation. |
segment_after_chunks | 0 (disabled) | Session segmentation (L3+): same, triggered by chunks completed since the last checkpoint. 0 disables it. |
segment_at_seam_tokens | 40000 (enabled) | Seam-aware segmentation (L3+, #960): when the next queued item crosses a cap/area boundary from the just-finished one, segment at this lower per-segment token bar instead of waiting for segment_after_tokens. Hybrid — a seam lowers the bar, never forces a segment. 0 disables the seam trigger; the full segmentation opt-out keeps it inert. |
revalidate_on_area_entry | true | Per-area approval re-validation (#1008): selecting an item across a cap/area seam arms a revalidating sub-mode of planning — a movement gate compares HEAD against the item's <!-- approved-at: sha --> marker, and only on movement does a lightweight AC re-check run (reversible drift auto-adjusted, irreversible surfaced). false restores trust-approval-blindly semantics. See Per-Area Re-Validation. |
phase_timeout_enforcement | warn | How max_phase_minutes breaches are handled: warn (advisory only), block (reject the offending transition until elapsed time recovers), or abort (route through budget_exceeded and auto-abort) |
doc_drift_gate | false | When true, the slash command fires commit_with_doc_gate after each commit, routing through the doc_drift_check phase before reporting. Mechanical drift is auto-fixed via /doc-sync; high-severity audit findings route the loop back to coding. See Doc Drift Gate. |
baseline_docs_check | true | At prerequisites, detect missing scaffolding docs on greenfield projects and file an unapproved req per gap (documentation,baseline-doc). Never authors docs (CAP-15 non-goal); idempotent; non-blocking. See Baseline Docs Check. |
baseline_docs | ["readme","runbook","claude"] | Which baseline docs are required. |
baseline_docs_max_commits | 25 | Greenfield gate: skip the check on repos with more commits than this. 0 = always run. |
baseline_docs_auto_approve | false | When true, filed tracking issues also get the approved label, so warp-drive writes the doc itself in a later cycle (crosses the human-authored-narrative boundary). |
phase_timeout_enforcement is the only budget threshold whose enforcement mode is configurable — retry_exceeded, coding_cycles_exceeded, no_progress, total_chunks_exceeded, and session_timeout are always enforced (they always route through budget_exceeded). Set phase_timeout_enforcement to warn (the default) for ordinary use and to block or abort only when you actively want phase-level timeouts to halt the loop.
For the full budget system, circuit breaker behavior, token capture pipeline, cost estimation, and reporting tools, see Token Monitoring & Budget System.
Verification Gates
You can configure custom shell commands that must pass before phase transitions:
json
{
"_workflow": {
"verification_gates": {
"code_complete": ["make lint"],
"tests_passed": ["make type-check"]
}
}
}If a gate command fails, the transition is blocked and Claude must fix the issue before advancing.
Doc Drift Gate
When _workflow.doc_drift_gate=true, the loop routes through a new doc_drift_check phase between committing and reporting:
committing → (commit_with_doc_gate) → doc_drift_check
↙ ↘
drift_clean drift_blocked
↓ ↓
reporting coding
(proceed normally) (fix high-severity drift)Inside the gate:
~/.claude/scripts/doc-keeper/sync.js --root . --applyruns and applies everyfix_kind: mechanicalfinding (component counts, label naming, etc.).~/.claude/scripts/doc-keeper/audit.js --root . --fail-on-driftre-audits.- If audit exits zero (no high-severity drift), the loop emits
drift_cleanand proceeds toreporting. Any sync edits are staged into a follow-up commit (or amended into the chunk commit, depending on policy). - If audit exits non-zero, the loop emits
drift_blockedand returns tocodingso Claude can address the high-severity findings before retrying the chunk.
The gate is opt-in (doc_drift_gate=false by default) for backward compatibility. When it's off, the standard committed → reporting transition still runs.
The hook hooks/doc-drift-warning.sh runs after every successful git commit — even when the doc_drift_gate phase above is disabled — and fires when a commit touches tooling source paths (skills/, commands/, agents/, hooks/, bin/, scripts/, Makefile, registry/) without touching any *.md. It has two policy modes (#757), read from .claude/settings.local.json:
- Advisory (default) — warns and lets the commit stand (exit 0).
- Blocking (
_workflow.doc_drift_blocking=true) — exits 2 so Claude Code surfaces a blocking error the agent must resolve (amend the commit with the doc update) before continuing. - Suppressed (
_workflow.suppress_doc_drift_warning=true) — silent no-op.
The gate is PR-independent: the hook fires at commit time on any branch, and the CI backstop runs make docs-check on push: branches:[master] (docs-deploy.yml) — neither depends on a PR event, so direct-merge (L3) work is gated identically to PR-routed work. Blocking mode is the local teeth; the push-stage CI gate is the non-bypassable backstop.
Baseline Docs Check
The Doc Drift Gate above keeps existing docs accurate. The baseline-docs check addresses the complementary gap: a greenfield project that never gets baseline scaffolding docs in the first place (a real miss observed on a fresh MVP scaffold — an operations runbook and project CLAUDE.md were never produced).
At prerequisites, scripts/warp-drive/baseline-docs-check.sh checks for a README, an operations runbook (RUNBOOK.md, runbooks/*.md, or docs/runbook*.md), and a CLAUDE.md. For each missing doc it files a GitHub issue labelled req,documentation,baseline-doc,p3-medium — without approved, so a human gates the writing.
Design boundaries (why it only surfaces the gap):
- Never authors narrative. Consistent with the CAP-15 non-goal —
README/CLAUDE.md/runbook prose stays human-authored. The loop files a tracking issue; a human (or, withbaseline_docs_auto_approve=true, a later cycle) writes it. - Idempotent. An open
baseline-docissue for a given doc is never refiled, so re-running prerequisites across sessions never duplicates. - Non-blocking. The check emits JSON and the loop continues regardless — it is advisory, never a gate.
- Greenfield-scoped. The
baseline_docs_max_commitsgate (default 25) skips mature repos, which already have these docs or have deliberately chosen otherwise. Set it to0to always run.
Configure via the baseline_docs* keys in Configuration. Disable entirely with baseline_docs_check=false.
Reasoning Budget
Control Claude's reasoning effort per phase (the "reasoning sandwich" pattern — high reasoning for planning/verification, standard for implementation):
json
{
"_workflow": {
"reasoning_budget": {
"planning": "high",
"coding": "standard",
"testing": "high"
}
}
}See Token Monitoring & Budget System for the full per-phase default table and how reasoning levels are applied.
What Gets Created
| Artifact | Where | Labels |
|---|---|---|
| Commits | Feature branch (conventional commit format) | — |
| Chunk reports | GitHub Issues | warp-drive |
| Session summary | GitHub Issue | session-summary, warp-drive |
| Lessons learned | GitHub Issues | lesson, warp-drive |
| Decisions made | GitHub Issues | decision, warp-drive |
| Deferred bugs | GitHub Issues | bug-deferred, warp-drive |
| TODO (timeouts) | GitHub Issues | todo, warp-drive |
| Abort reports | GitHub Issues | warp-drive, session-summary |
| PR (Level 2) | GitHub PR via gh pr create | — |
GitHub labels are created automatically on first run (warp-drive, lesson, decision, risk, bug-deferred, session-summary).
Recovery
If a session is lost (crash, worktree deletion, etc.), use the disaster recovery CLI (Terminal only — these recovery scripts have no slash form and are meant to run when no session is alive):
bash
# Check current state
~/.claude/scripts/lib/cdr.sh status
# Re-initialize tooling (clone from remote, re-run cdi)
~/.claude/scripts/lib/cdr.sh recover /path/to/project
# Emergency state reset (deletes warp-drive state file)
~/.claude/scripts/lib/cdr.sh reset /path/to/projectCommit hash recovery
Warp-drive records commit hashes in the state file (commit_hashes[]). If the state file survives but the worktree is lost, cherry-pick commits from the remote:
bash
git fetch origin
git cherry-pick <hash1> <hash2> ...Preflight check
Run the preflight checker to verify all dependencies are installed (Terminal only):
bash
~/.claude/scripts/lib/preflight-check.shThis checks: jq, node, git, gh auth, hook file existence, and core scripts.
Troubleshooting
"Automation level must be 2 or 3"
Run /automation level 2 inside Claude Code first. This sets up permissions in .claude/settings.local.json.
"Must not be on main/master"
Run /start-work to create a feature branch (Level 2), or let Level 3 auto-create one.
Warp-drive won't start — "Active session exists"
A previous session didn't clean up. Check if it's stale:
bash
warp statusIf stale, reset it:
bash
warp stop --hardCommit blocked — "only allowed during committing phase"
The gate hook is working correctly. Claude tried to commit outside the committing phase. This usually means a phase was skipped. Check the state:
bash
warp logCan't exit Claude Code — "report is missing"
The stop hook blocks exit when a commit was made but no chunk report was filed. Tell Claude to file the report, or if you need to force exit, kill the terminal.
Doom loop warning
Claude keeps editing the same file. The edit tracker caught it. This usually means the approach isn't working. Claude should try an alternative strategy or escalate.
State file is corrupted
bash
warp stop --hardThis deletes the state file. Your code changes are still in the working tree. Start a new session when ready.
warp command not found
Add ~/.claude/bin to your PATH:
bash
echo 'export PATH="$HOME/.claude/bin:$PATH"' >> ~/.zshrc
source ~/.zshrcInternals Reference
Moved to reference — see Warp-Drive State Reference for the state file, state-machine CLI, and transition table.