cdfork Guide
cdfork — multi-agent worktree orchestration
Section titled “cdfork — multi-agent worktree orchestration”bob fork is BoB’s fan-out tool. It spawns N parallel /warp-drive sessions in
isolated git worktrees, each in its own tmux window, so you can run independent
work clusters in parallel without stepping on yourself.
Tmux + git are the state. There is no daemon, no state file, no database. If the orchestrator dies, your worktrees and commits are still intact and you can resume by attaching the tmux session.
Two ways to run every command
Section titled “Two ways to run every command”Every bob fork command below works from both worlds — pick whichever you’re in:
- Terminal —
cdfork <sub>(thebin/cdforkalias shim, which execsbob fork→scripts/cli/cdfork;bob fork <sub>is the same thing spelled out, #2060). - Slash command —
/cdfork <sub>inside a Claude Code session; the/cdforkcommand is a thin wrapper that forwards every argument to the samescripts/cli/cdforkengine, so the flags are identical.
So bob fork fork branch-a branch-b and /cdfork fork branch-a branch-b do the
same thing. The tables and examples below write the Terminal form; prefix any
of them with / to get the slash form. (bob pair is the one exception — see
that section.)
Subcommands
Section titled “Subcommands”The Command column shows the Terminal form (bob fork …); each has the
matching Slash command /cdfork … (e.g. /cdfork status).
Command (Terminal — prefix / for the slash form) |
Purpose |
|---|---|
bob fork fork <branch>... |
Spawn one warp-drive session per branch |
bob fork fork --from-issues [N] |
Auto-discover N approved requirement issues, derive branches, fan out |
bob fork fork --from-flightplan <N> |
Fan out flight-plan #N’s runnable wave — independent items in parallel, dependents held (#1613) |
bob fork status [--json] |
List active worktrees + their tmux + warp-drive state |
bob fork drop <branch> |
Safely remove a worktree + its tmux window |
bob fork drop --all |
Drop every cdfork worktree for the current repo |
bob fork watch [--once] [--json] |
Label each window working / idle / needs-attention and raise a terminal notification when a run waits on you (fork starts it — see Window labels and notifications); in a --from-flightplan session it also starts the items a landed item was blocking (Flight-plan fan-out) |
bob fork pair --contract --backend --frontend --branch |
Cross-repo contract-driven parallel mode |
bob pair ... |
Standalone shape of bob fork pair (Terminal only — no /cdfork-pair; use /cdfork pair) |
bob fork help |
Show command summary |
Installation
Section titled “Installation”cdfork is part of BoB. Once you’ve run bob-install.sh (which adds the BoB
shell-wrapper block to your .bashrc / .zshrc), bob fork is available as a
PATH entrypoint — bin/cdfork, the alias shim for bob fork (engine
scripts/cli/cdfork, #2060; the shim retires 2026-09-21). Run
source ~/.zshrc (or open a new shell) to activate.
The standalone bob pair engine lives at scripts/cli/cdfork-pair (bob pair;
bin/cdfork-pair is its alias shim) and is invoked
the same way once BoB is on PATH.
Prerequisites
Section titled “Prerequisites”| Tool | Why | Install |
|---|---|---|
git |
Worktrees | macOS: brew install git. Linux: distro package. |
tmux |
Window management | macOS: brew install tmux (not installed by default!). Linux: apt install tmux / dnf install tmux. |
claude |
Spawned in each window | The Claude Code CLI. Available globally once Claude Code is installed. The BoB claude() shell wrapper handles the -a1/-a2/-a3 automation flags. |
gh |
--from-issues |
brew install gh / distro package. |
jq |
Status JSON, from-issues/from-flightplan filter | brew install jq / distro package. |
node |
--from-flightplan (runs flightplan.js remaining) |
Already required by BoB’s scripts. |
bob fork’s preflight check runs before any worktree is touched and fails fast
if any of these are missing. It guards a real launch only: bob fork help,
bob fork fork --help and bob fork pair --help print usage on any machine,
including one with no claude binary on PATH such as the CI runner (#2345).
Worktree layout
Section titled “Worktree layout”For a repo at /path/to/<repo>/, cdfork puts worktrees at:
/path/to/<repo>.worktrees/<branch>/Sibling directory, not nested inside the repo. This keeps Drupal/Acquia-strict roots clean and avoids confusing composer (which traverses the dir tree looking for vendor/).
For branches with slashes (feat/foo), git creates the intermediate dir
naturally — you’ll see <repo>.worktrees/feat/foo/.
Tmux convention
Section titled “Tmux convention”| Mode | Session | Window |
|---|---|---|
bob fork fork |
cdfork-<repo> |
one per branch, named <branch> |
bob fork pair |
cdfork-pair-<branch> (slashes → _) |
<branch>-be and <branch>-fe |
To attach: tmux attach -t cdfork-<repo> (or the pair session name printed in
the summary).
Window labels and notifications (#3549)
Section titled “Window labels and notifications (#3549)”Each bob fork fork window is relabelled <branch> [<state>], so the tmux
status line, and the iTerm2 tab under tmux -CC, shows how that run is doing:
| Label | When |
|---|---|
working |
the worktree’s warp run is in any working phase |
idle |
no warp state file, or the run is idle / completed |
needs-attention |
the run is blocked at preflight, at budget_exceeded, parked on the forge, waiting at awaiting_continue below Level 3, or has a handoff record that stops it (the human handoff record) |
The label comes from the worktree’s .claude/.warp-drive-state.json and
.claude/.warp-handoff.json, never from the screen. When a run enters
needs-attention, the watcher writes one OSC 9 and one OSC 777 notification
to that window’s pane, so a terminal that supports them (cmux, iTerm2,
Ghostty) raises it. A run that stays waiting is not notified again.
bob fork fork starts one watcher per session in the background. It checks
every 15 seconds and exits when the cdfork-<repo> session ends. Set
CDFORK_WATCH=0 to fork without it, and run it yourself with
bob fork watch (--interval <seconds> to change the pace, --once --json
for one pass and a report). It works the same on macOS and on Linux tmux
(farm-01).
This sits beside the remote decision bridge (/rdb) and
bob warp viz; it does not replace them.
Why tmux send-keys and not tmux new-window <cmd>
Section titled “Why tmux send-keys and not tmux new-window <cmd>”cdfork uses tmux send-keys to type the launch command into each window
after the shell has loaded its rc file. Since #2049 the line is sent by the
shared launcher scripts/warp-drive/launch.sh (send), the same engine behind
bob warp start — CDFORK_LEVEL (default 3) and CDFORK_RDB=1 are its
parameters here. This is critical because the BoB
claude() shell wrapper (which intercepts -a1/-a2/-a3 and routes them to
switch-level.sh) is defined in your shell init, not on PATH. If we used
tmux new-window <cmd>, the wrapper wouldn’t exist when <cmd> ran, and
the automation level wouldn’t be set.
The send-keys approach also lets users see the literal command land on screen, so it’s obvious what’s about to run.
Mode 1 vs Mode 2 usage
Section titled “Mode 1 vs Mode 2 usage”Mode 1 — Laptop, interactive. bob fork fork branch-a branch-b, attach the
tmux session, watch the agents work, intervene when they hit a decision.
Intended use: 2–3 sessions in parallel. Keeps a human in the loop on each.
Mode 2 — farm-01, unattended. Same command, headless. Each warp-drive
session runs at automation Level 3, only escalating to GitHub Issues for
genuine project decisions. SSH in occasionally to check bob fork status or
attach the tmux session to spot-check progress. Intended use: 4+ sessions
overnight or during the day on independent module clusters.
Pinch-point rules — the serial-only label
Section titled “Pinch-point rules — the serial-only label”Parallelism works for module code, frontend code, tests, and cleanup sweeps.
Parallelism breaks for single-source-of-truth changes:
- Drupal config sync —
config/sync/is one tree. Two parallel branches exporting config will conflict. - Composer / pnpm lockfiles — one lockfile, deterministic resolution.
- Database migrations /
update.php— schema must apply in order. - Compiled theme assets — committed build output (e.g. example-admin-theme, example-theme) belongs to one builder.
- Acquia deploy-gated work — single deploy pipeline.
Issues with these characteristics should carry the serial-only label.
bob fork fork --from-issues excludes them from auto-discovery by default.
Pass --include-serial only for the rare case where you’ve manually
verified the work doesn’t actually touch a pinch point.
Preflight once, then fan out (#1923)
Section titled “Preflight once, then fan out (#1923)”bob fork fork --from-issues runs the warp-drive preflight
gate once, against the parent checkout,
before any worktree or window exists — bob doctor (is this machine’s
BOB_HOME converged with source?) plus bob ready --dry-run (is this project
warp-drive-ready?). N sessions would otherwise each hit the same gate and each
file the same todo. A failed gate ends the fan-out with nothing spawned and the
todo naming each fix; --dry-run only reports the verdict (it files nothing and
still prints the plan). CDFORK_PREFLIGHT=0 skips the gate — for hermetic
tests, never as a way past a failing host.
This rule is client-specific in principle. Other projects can reuse the label if they have similar single-source-of-truth concerns (seebod migrations, bodmail pnpm workspaces, etc.) — the label is just a flag, the concept of what’s serial is project-specific.
bob fork pair — cross-repo contract-driven
Section titled “bob fork pair — cross-repo contract-driven”Anchor use case: the Client A headless migration (example-cms ↔
example-web). Whenever a feature spans a backend repo and a frontend repo,
both sides can move in parallel as long as they agree on a contract — request
shape, response shape, error semantics. cdfork pair codifies that.
Terminal (either the bob fork pair subcommand or the standalone bob pair bin):
bob fork pair \ --contract docs/contracts/CAP-04.md \ --backend ~/Sites/example-co/example-cms \ --frontend ~/Sites/example-co/example-web \ --branch feat/cap-04-product-detailSlash command (/cdfork pair — same flags; there is no /cdfork-pair):
/cdfork pair --contract docs/contracts/CAP-04.md --backend ~/Sites/example-co/example-cms --frontend ~/Sites/example-co/example-web --branch feat/cap-04-product-detailEach side gets a worktree on the same branch name, plus a tmux window in a
shared session, plus a Claude session launched with
--append-system-prompt carrying the absolute path to the contract — so
warp-drive starts with the contract already in its system prompt and reads
it before any work.
The contract document is the integration point. If both agents implement to the same contract, integration at the end is just running the cross-repo test suite.
Auto-discovery (--from-issues)
Section titled “Auto-discovery (--from-issues)”Terminal (prefix / for the /cdfork fork --from-issues … slash form):
bob fork fork --from-issues 4 # default 4bob fork fork --from-issues 6 # widen the fan-outbob fork fork --from-issues 4 --dry-run # show plan, don't spawnbob fork fork --from-issues 4 --include-serial # include serial-only issuesPicks up to N approved requirement issues (--label req --label approved --state open) and derives branch names: <type>/<number>-<slug>.
Type heuristic on the title:
| Title prefix | Type |
|---|---|
Fix* |
fix/ |
Add* / Implement* / Build* / Introduce* / Create* |
feat/ |
Refactor* |
refactor/ |
Doc* / Docs* |
docs/ |
| anything else | chore/ |
The leading kind-word is stripped from the slug to avoid fix/301-fix-....
Fanning out a fresh instruction, not pre-existing issues? The swarm decompose engine splits one free-text instruction into N independent subtasks and emits the same
<branch>\t<id>\t<title>list cdfork consumes — with overlap and pinch-point analysis up front.
Flight-plan fan-out (--from-flightplan, #1613)
Section titled “Flight-plan fan-out (--from-flightplan, #1613)”When a flight plan declares depends_on edges, its runnable
set at any moment is the independent wave — items whose prerequisites are
all complete. --from-flightplan fans out exactly that wave:
bob fork fork --from-flightplan 1615 # spawn the current runnable wavebob fork fork --from-flightplan 1615 --dry-run # preview it-
The wave comes from
flightplan.js remaining <N> --json— the same single gate-resolution path warp-drive consumes, so an itemwaitingon an unmetdepends_on,gatedby a pending todo, orwithheldbehind a barrier is never spawned. -
Each worktree session is scoped to its own item (
/warp-drive --issue <NN>), so no session widens beyond its assignment. A closed issue counts as a complete prerequisite, so dependents release as the parallel sessions land their work. -
Dependents start themselves (#3550). The session remembers its plan, and
bob fork watch(whichforkstarts) dispatches the next items when a forked run ends: it re-runs the same--from-flightplanpath, so an item whose last prerequisite has just been checked or closed is spawned in its own scoped worktree. The blockers come from the plan, never from an agent’s prompt. Consumption is wave by wave and finishes in about critical-path time (flightplan.js status <N>reports the critical path; the plan’s per-item check-off timestamps are the receipts). Re-runningbob fork fork --from-flightplan <N>yourself does the same and is safe: an item that already has a worktree is not spawned again. -
A cap on live windows. At most
_workflow.fork_max_parallel(default 4) plan items run at once. A forked item still open in the plan holds a slot; a landed one frees it. The first wave and every later dispatch fill the free slots in plan order, andforknames the items left waiting:cdfork fork --from-flightplan #1615: runnable wave (cap 4, 2 live):would spawn 2 session(s): fix/1622-… feat/1623-…waiting for a slot (1): #1624 -> fix/1624-… — bob fork watch dispatches them as slots freeSet the cap in the project manifest’s
_workflowor in.claude/settings.local.json(bob warp configshows the value in force). -
bob fork watch --once --jsonruns one pass by hand: itsdispatchfield names the plan, the branches it spawned and how many still wait.--no-dispatchonly labels windows. -
serial-onlyitems are excluded exactly as with--from-issues(--include-serialoverrides) — pinch points stay out of parallel worktrees. -
“No runnable items right now” (exit 1) means the current wave is either done or blocked; inspect with
flightplan.js remaining <N> --all --json.
Troubleshooting
Section titled “Troubleshooting”“missing required commands” on bob fork fork — Run the install commands
under Prerequisites. The check runs before any side effect.
Worktree already exists — bob fork drop <branch> (or bob fork drop --all)
to remove. If commits weren’t pushed, drop will warn but proceed.
Worktree refuses to drop (“uncommitted changes”) — that’s the guardrail.
Either commit/stash the changes inside the worktree, or pass --force if
you’re sure you want to throw them away.
tmux session got killed accidentally — your worktrees and commits are
intact. Just bob fork status to inspect, then either re-attach with
tmux new-session -d -s cdfork-<repo> and re-launch the agents manually,
or bob fork drop --all and start fresh.
A window never shows a label, or no notification appears — run
bob fork watch --once --json in the repo: each worktree’s label and
reason say what the watcher read, and window: null means it found no
window for that branch. Notifications need tmux 3.3+ with passthrough (the
watcher turns allow-passthrough on for the pane) and a terminal that shows
OSC 9 or OSC 777; in iTerm2 turn on notifications for the profile.
--from-issues finds nothing — check gh issue list --label req --label approved --state open. The query has to return at least one match. If
issues are missing the approved label, that’s the gate.
--from-issues is in the wrong repo — gh queries the current repo.
cd to the project first.
Recovery
Section titled “Recovery”If something goes wrong mid-spawn (network glitch, disk full, half a session came up), the recovery path is:
bob fork statusto see what got created.bob fork drop <branch>for any half-spawned worktrees.- Re-run the original
bob fork fork ...command. It’s safe to re-run — existing worktrees are skipped with a warning.
See also
Section titled “See also”- Warp Drive Guide — what each agent does inside its worktree
- Vision: cdfork on Now roadmap
- Research: parallel-agent-orchestration
- Source:
scripts/cli/cdfork,scripts/cli/cdfork-pair(alias shimsbin/cdfork,bin/cdfork-pair),scripts/cdfork/*.sh - Smoke tests:
tests/test-cdfork-*.sh