cdfork — multi-agent worktree orchestration
cdfork 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.
Subcommands
| Command | Purpose |
|---|---|
cdfork fork <branch>... | Spawn one warp-drive session per branch |
cdfork fork --from-issues [N] | Auto-discover N approved requirement issues, derive branches, fan out |
cdfork status [--json] | List active worktrees + their tmux + warp-drive state |
cdfork drop <branch> | Safely remove a worktree + its tmux window |
cdfork drop --all | Drop every cdfork worktree for the current repo |
cdfork pair --contract --backend --frontend --branch | Cross-repo contract-driven parallel mode |
cdfork-pair ... | Standalone shape of cdfork pair |
cdfork help | Show command summary |
Installation
cdfork is part of BoB. Once you’ve run bob-install.sh (which adds the BoB
shell-wrapper block to your .bashrc / .zshrc), cdfork is available as a
shell alias that delegates to ${BOB_SOURCE:-$HOME/.claude}/bin/cdfork. Run
source ~/.zshrc (or open a new shell) to activate.
The standalone cdfork-pair script lives at bin/cdfork-pair and is invoked
the same way once BoB is on PATH.
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 filter | brew install jq / distro package. |
cdfork’s preflight check runs before any worktree is touched and fails fast
if any of these are missing.
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
| Mode | Session | Window |
|---|---|---|
cdfork fork | cdfork-<repo> | one per branch, named <branch> |
cdfork 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).
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. 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
Mode 1 — Laptop, interactive. cdfork 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 cdfork 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
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. nanaclaro, nanawall22) belongs to one builder.
- Acquia deploy-gated work — single deploy pipeline.
Issues with these characteristics should carry the serial-only label.
cdfork 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.
This rule is nanawall-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.
cdfork pair — cross-repo contract-driven
Anchor use case: the nanawall headless migration (nanawalld8 ↔
nanawall-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:
cdfork pair \
--contract docs/contracts/CAP-04.md \
--backend ~/Sites/nanawall/nanawalld8 \
--frontend ~/Sites/nanawall/nanawall-web \
--branch feat/cap-04-product-detail
Each 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)
cdfork fork --from-issues 4 # default 4
cdfork fork --from-issues 6 # widen the fan-out
cdfork fork --from-issues 4 --dry-run # show plan, don't spawn
cdfork fork --from-issues 4 --include-serial # include serial-only issues
Picks 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-....
Troubleshooting
“missing required commands” on cdfork fork — Run the install commands
under Prerequisites. The check runs before any side effect.
Worktree already exists — cdfork drop <branch> (or cdfork 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 cdfork status to inspect, then either re-attach with
tmux new-session -d -s cdfork-<repo> and re-launch the agents manually,
or cdfork drop --all and start fresh.
--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
If something goes wrong mid-spawn (network glitch, disk full, half a session came up), the recovery path is:
cdfork statusto see what got created.cdfork drop <branch>for any half-spawned worktrees.- Re-run the original
cdfork fork ...command. It’s safe to re-run — existing worktrees are skipped with a warning.
See also
- Warp Drive Guide — what each agent does inside its worktree
- Vision: cdfork on Now roadmap
- Research: parallel-agent-orchestration
- Source:
bin/cdfork,bin/cdfork-pair,scripts/cdfork/*.sh - Smoke tests:
tests/test-cdfork-*.sh