| name | autopilot-batch |
| description | Fan out a batch of autopilot-queued issues to parallel background worktree subagents — each runs /autopilot at the build model from its 'model:' label — with a gating review at Opus 5 or above and never below the build (Opus reviews Sonnet and Opus builds, Fable reviews Fable builds). |
| disable-model-invocation | true |
| argument-hint | [--merge <issue#,…>] |
Autopilot Batch
Run the vetted autopilot-queued queue as a parallel batch: one background isolation: worktree subagent per issue, each running /autopilot <n> end-to-end at the build model its model: label calls for, with a gating review at Opus 5 or above and never below the build (Opus for Sonnet- and Opus-built PRs, Fable for Fable-built PRs). This is the run half of the triage → run split; /autopilot-triage is the vet half that fills the queue.
Use this when you have a queue of independent, well-scoped issues and want them all carried to review-ready PRs in one unattended pass. For a single issue, use /autopilot directly.
Where this runs (read first)
Run this from the target application repository — the repo whose issues and app these are (e.g. the Rails app) — on a clean default branch, pulled up to date. Not from dotfiles. isolation: worktree creates each worktree from the orchestrator's current repo, so the cwd decides where the fan-out worktrees land; running from the wrong repo produces worktrees of the wrong tree. If the cwd is the dotfiles repo (or any repo that doesn't own these issues), stop and say so.
Arguments
- (no args) — run every
autopilot-queued issue at tier --to pr. The whole batch stops at review-ready PRs; nothing merges.
--merge <issue#,…> — authorize tier --to merge for the listed issues only (e.g. --merge 847,851). Everything else stays --to pr. Merge is still gated per issue by /autopilot's Step 8 narrow-class gate, which degrades any non-qualifying issue back to --to pr. Passing --merge IS your per-issue authorization to merge those issues; it is not a standing capability.
Default tier is --to pr by design — merge is opt-in, per issue, never batch-wide.
The build-model policy — the label decides the build, review runs at Opus or above
Each issue's build subagent runs at a model chosen per issue; the gating review always runs at Opus 5 or above, and never below the build. Ladder, cheapest to most capable: Sonnet 5 → Opus 5 → Fable 5.
Resolving each issue's build model
Read the issue's model: label first. Repos that have adopted the per-issue model convention (~/.claude/docs/model-selection-strategy.md) carry one on every open issue, recording the build tier assigned at triage. Step 1's issue list already returns labels, so no extra call is needed:
model: sonnet → build with Sonnet 5; model: opus → Opus 5; model: fable → Fable 5.
- No
model: label → fall back to the class rubric below. Most repos have not adopted the convention and must keep working unchanged; in an adopted repo, unlabeled means Opus by convention — which the rubric already approximates — so the same fallback is right in both worlds.
The fallback rubric, unchanged:
- Opus 5 — data-model / migrations, auth / security boundaries, inventory / shipment correctness, thin or ambiguous specs, cross-cutting refactors. Also the model whenever you genuinely can't tell the class (safe default — Opus never under-builds).
- Sonnet 5 — bounded, well-specified, pattern-following work: i18n / copy, views / Tailwind, config, a single test, straightforward CRUD, docs.
State each issue's model at the confirm gate and where it came from — label or rubric (no label) — with a one-line why for every rubric call. An override you make at the gate is worth writing back to the issue's label afterward, so the record stays honest for the next run.
The review tier is derived, never labeled
The gating review (Step 4) is computed from the build tier and is never read from the label. One invariant: review runs at Opus 5 or above, and never below the build.
| Build | Gating review |
|---|
| Sonnet 5 | Opus 5 |
| Opus 5 | Opus 5 (fresh context, adversarial prompt, independent sample) |
| Fable 5 | Fable 5 (ceiling) |
This is the structural safety net: an under-build is caught and fixed at review, never shipped. That is exactly what makes leaning on the cheaper tier for the bulk close to free on quality while saving real latency and limit headroom at fan-out scale — so a model: sonnet label must never downgrade the review. It sets the build only.
The floor replaced a "one tier above the build" rule on 2026-07-27. That rule was written to insure cheap builds; Opus was never the cheap build, and at fan-out scale it sent nearly every review in the batch to Fable. A Fable review is now reached by escalation rather than by rule — via a model: fable label, via a mid-flight escalation (the table reads the model the build actually ran at), or by simply re-spawning the reviewer at Fable on the PR. See ~/.claude/docs/model-selection-strategy.md § "The review floor, and the trade it accepts" for the reasoning and the trade it knowingly accepts.
Merge issues are always Fable-built
A build subagent runs the whole /autopilot loop at one model (a subagent can't switch mid-run), so whichever model builds a --to merge issue also makes its merge go/no-go inside /autopilot Step 8. The floor is non-negotiable: the merge decision is always Fable — the autonomous merge is the highest-stakes call in the pipeline, so it gets the most capable model. Therefore: any issue opted into --to merge is built by Fable, regardless of what its model: label or the class rubric would otherwise pick — a model: sonnet label on a merge-tier issue does not buy a cheaper merge decision. Sonnet and Opus only ever build --to pr issues, which stop at a review-ready PR and pass the gating review (Step 4) before you see them. This keeps every autonomous merge Fable-decided without the orchestrator having to re-implement the merge gate. The cost — running the rare, narrow merge class at Fable rates — is negligible because --to merge is opt-in and uncommon.
Escape hatch (batch-level)
A single issue stopping must not halt the batch. If a build subagent hits /autopilot's own escape hatch (a complex/ambiguous issue, a guardrail decision, an unresolvable failure), it stops and reports — that issue is left for you, keeps its queue label, and the rest of the batch continues. Surface every stop prominently in the final report. Stop the whole batch only for a systemic problem: the cwd is the wrong repo, auth to the remote is failing for everyone, or the queue is empty.
Your task
Announce the run first: how many issues are queued, the tier split (all --to pr, or which are --to merge), and that you will fan out one background worktree subagent per issue.
Step 0 — Preconditions
- Confirm the cwd is the target app repo (see "Where this runs"), on a clean default branch, then
git pull.
- Pre-warm one push approval. Run a single
git ls-remote origin up front so any SSH-agent signing approval is granted once, before fan-out — the batch then runs unattended. (Per-push resilience during the run is already handled inside /autopilot: transient sign_and_send_pubkey … communication with agent failed errors are retried with backoff.)
Step 1 — Assemble the batch
gh issue list --label autopilot-queued --state open --json number,title,labels.
- If the list is empty: stop — nothing is queued. Recommend
/autopilot-triage to vet candidates and fill the queue.
- Build the run plan: for each issue, its assigned build model, its source (
label or rubric (no label), with a one-line why for rubric calls), and its tier (pr, or merge if named in --merge — and remember a --merge issue is forced to a Fable build whatever its label says).
- Note any queued issue missing a
model: label in a repo that has adopted the convention — check with gh label list --json name --jq '[.[].name | select(startswith("model: "))]'; a non-empty result means adopted. Missing labels are a triage gap, not a blocker: the rubric covers the issue, but flag them at the gate so triage can be corrected.
CONFIRM GATE: Present the run plan — issue list, per-issue model + source, per-issue tier — and get a go-ahead before spawning. Membership was already vetted at triage; this is a light confirm that the models and tiers are right and the queue is still current, plus your chance to override a model call. Not a re-litigation of the queue.
Step 2 — Fan out the builds
First, write each issue's isolated-command wrapper. Do this yourself, before spawning anything — one file per issue, at /tmp/autopilot-i<n>, chmod +x:
#!/usr/bin/env bash
set -euo pipefail
if [ "$(git rev-parse --git-dir 2>/dev/null)" = "$(git rev-parse --git-common-dir 2>/dev/null)" ]; then
echo "autopilot-i<n>: run this from the agent's worktree, not $PWD" >&2
exit 1
fi
export PARALLEL_WORKERS=1 RAILS_ENV=test DATABASE_URL=postgres:///<app>_test_i<n>
case "${1:-ci}" in
ci) exec bin/ci ;;
test) shift; exec bin/rails test "$@" ;;
prepare) exec bin/rails db:test:prepare ;;
*) echo "usage: $(basename "$0") [ci|test <args>|prepare]" >&2; exit 2 ;;
esac
This is the whole point of the mechanism: the three variables are typed once, by you, instead of re-threaded onto every command by an agent for the length of a run. The Bash tool does not persist shell state between calls, so an export at the start of an agent's run does nothing and the variables would otherwise have to be re-typed on every test, CI, and db:test:prepare invocation — including the ones initiated from inside /autopilot, which the agent invokes rather than controls. A wrapper cannot be half-remembered.
Three deliberate choices:
- It covers all three call sites, not just CI.
ci for the full pipeline, test for the fast checks /resolve-issue Step 5 and /autopilot Step 4 need mid-loop, prepare for the one-time database setup. A CI-only wrapper would leave the loop with no isolated way to run tests — and an agent that needs one and hasn't got one will reach for bare bin/rails test, which is exactly the unprefixed invocation that leaked twelve worker databases on 2026-07-27. Forbidding a command without supplying its replacement produces the violation rather than preventing it.
- It lives in
/tmp, not in bin/. Build agents commit frequently, and a bin/ci-isolated inside the worktree is an untracked file that git add -A would sweep into the PR.
- It guards its cwd instead of
cd-ing. Not cd-ing is what lets one wrapper serve the build agent and the later review agent in their different worktrees. But "wrong cwd fails loudly" is only true for directories that lack bin/ci — and the main checkout has one, so an invocation from there would have run CI against the wrong tree and signed off on the wrong branch, silently. The worktree assertion closes that.
Then, for each issue, spawn a background subagent:
isolation: worktree — its own checkout, so parallel edits across issues can't collide.
run_in_background: true — they run concurrently.
model: the issue's assigned build model — from its model: label, or the rubric when it has none; Fable for any --to merge issue. Because the subagent is spawned at that model, /autopilot's own announce-time reconciliation normally finds a match and passes straight through. If you spawn an agent below its issue's label — a downgrade you approved at the confirm gate — say so explicitly in the prompt ("building at Opus is a deliberate override of this issue's model: fable label, approved at the batch confirm gate"), or the agent will treat it as an under-build and stop.
- prompt: if the issue body opens with a model callout (a
> 🤖 Recommended model: … blockquote), quote it in the prompt — it names the watch-item or escalation trigger behind the tier choice, and it is wasted if only the orchestrator reads it. Then: run /autopilot <n> --to <tier> to completion, with this instruction stated explicitly — every test, CI, and database command for this entire run goes through /tmp/autopilot-i<n>, run in the foreground from your worktree root: … prepare once before anything else, then … ci for the full pipeline and … test [args] for the fast checks mid-loop. Never bare bin/ci, never bin/rails test. This holds inside /autopilot too — its Step 7 defers to the CI command you were given. (See the foreground-CI note under Important; the wrapper carries the serial and isolated-database settings, so there is nothing to re-type and nothing to remember.) Then report back, as the final message, the PR number and URL, the loop outcome (which steps ran / were n·a), a one-line summary of the change, and, if it stopped at the escape hatch, exactly where and why.
Spawn them together so they run in parallel. Each subagent lands on an auto-named worktree branch (worktree-agent-…); inside it, /autopilot → /resolve-issue creates the proper feat/gh-<n>-… branch, commits, pushes, and opens the PR from it. Nothing to pre-create.
Step 3 — As each build finishes
When a build subagent reports a review-ready PR:
- Drop the queue label:
gh issue edit <n> --remove-label autopilot-queued. The issue is now in-flight, not pending — lifecycle-contract step 3.
- Remove that issue's build worktree before spawning its review —
git worktree remove --force <path>, using git worktree list to find it. The build agent is finished and its work is pushed, so the worktree is dead weight; but leaving it in place is what causes the gh signoff failure described in Step 4. While it exists it still holds the PR branch checked out, so the reviewer's gh pr checkout <PR> cannot take that branch and silently leaves it on a detached HEAD. Removing it first makes the checkout ordinary and the whole failure mode disappear.
- Kick off its gating review (Step 4) right away — don't wait for the whole batch: Opus for a Sonnet or Opus build, Fable for a Fable build. (A
--to merge issue was Fable-built and Fable-reviewed inside its own /autopilot run — no orchestrator review needed.)
A subagent that stopped at the escape hatch keeps its autopilot-queued label (still pending) and is set aside for the final report — do not review or merge it.
Step 4 — Gating review (every --to pr build)
For each review-ready PR, spawn a review subagent per the derivation table above — Opus for a Sonnet-built or Opus-built PR, Fable for a Fable-built PR. Derive this from the model the build actually ran at, not from the issue's model: label: if a build was overridden at the confirm gate or escalated mid-flight, the review must follow the real build tier, not the recorded one. That is also what makes a mid-flight escalation to Fable pull its own review up with it.
model: opus (Sonnet or Opus build) or model: fable (Fable build), isolation: worktree.
- prompt: check out the PR branch (
gh pr checkout <PR>), run the built-in review <PR> — it takes a PR number and no effort level, so depth comes from the spawn tier above plus, for a large or security-/data-sensitive diff, fanning out additional adversarial lenses; mutation-test the key claims either way (delete or disable the change and confirm the covering test actually fails — a test that stays green with the feature removed is the miss a read-only review cannot see). Then fix real correctness bugs and clear quality wins, commit and push (this updates the open PR), then re-run CI with /tmp/autopilot-i<n> ci — the same wrapper the build agent used, so the review reuses that issue's database — so the required sign-off attaches to the final commit. Its test mode is available for iterating on a fix without paying for the full pipeline each time. Report the verdict: clean / N fixes applied / a finding that needs a product decision (→ flag it for the human, don't guess).
- Also state in the prompt: if
gh signoff fails with current branch is not tracking a remote branch, you are on a detached HEAD — gh pr checkout could not take the branch. It is the only red step in an otherwise green run and reads like a test failure, which is what makes it expensive. Step 3 removes the build worktree first precisely to prevent this, so if it still happens, say so rather than working around it silently. The recovery is either a throwaway tracking branch (git switch -c signoff-<PR> then push with -u), or — after confirming the tree is clean and HEAD equals origin/<pr-branch> — gh signoff create -f.
This is the review floor: every --to pr build is reviewed at Opus or above — never below itself — before the PR is "review-ready" for you, so a build-model miss is caught and fixed, never shipped. If a review comes back thin on a diff you are uneasy about, re-spawning the reviewer at Fable on the same PR is the escalation path: one review's worth of headroom, invoked by judgment rather than by rule. --to merge issues don't pass through here — they were Fable-built and Fable-reviewed inside their own /autopilot run, and merged there only if the narrow-class gate passed.
Step 5 — Reclaim worktrees
Step 3 already removed the build worktree of every issue that reached a review-ready PR. What remains: the review worktrees, the build worktrees of --to merge issues (they merged inside their own run and never passed through Step 3), and the build worktree of anything that stopped at the escape hatch — that last one deliberately, since its work is unpushed and removing it would discard the run.
Don't work from that list; work from git worktree list, which is authoritative. git worktree remove --force <path> each leftover …/worktrees/agent-* entry except any belonging to a stopped issue, then git worktree prune. The branches live on the remote (pushed as feat/gh-<n>-…), so removing the local worktrees is safe. Delete any signoff-<PR> helper branches a reviewer had to create (see Step 4), and remove the per-issue wrappers: rm -f /tmp/autopilot-i*.
Drop the per-issue test databases too — they are the batch's largest leftover and nothing else reclaims them:
psql -lqt | cut -d'|' -f1 | tr -d ' ' \
| grep -E "^<app>_test_i[0-9]+([-_][0-9]+)?$" \
| while read -r db; do dropdb "$db" || echo "still in use, skipped: $db"; done
dropdb fails on a database that still has an open connection — a stray agent or a psql you left open. That is why the loop tolerates a failure and names the database instead of aborting the sweep; re-run it once the connection is gone.
The _i[0-9]+ segment is what keeps this off the project's own <app>_test and its worker databases, which belong to the human's local runs — the pattern matches only databases this batch created. Run the leak check under Important before this, not after: dropping the evidence first would hide a bypass you needed to know about.
Completion report
Post a batch summary:
## Autopilot batch — N issues
| Issue | PR | Build | Review | Outcome |
| ----- | -- | ----- | ------ | ------- |
| #847 | #M | Fable (merge tier) | (internal) | merged + deployed ✓ |
| #851 | #P | Sonnet (label) | clean (Opus) | PR-ready |
| #863 | #Q | Opus (rubric) | 2 fixes (Opus) | PR-ready |
| #870 | — | — | — | STOPPED — <reason>, needs you |
- Queue: <k> resolved to PRs, <j> stopped and still labeled `autopilot-queued`.
- Model labels: <none missing | #N, #M had no `model:` label in an adopted repo — triage gap, ran on the rubric>.
- Merged: <list, or none — all held at PR-ready>.
- Your call: review + `/merge-pr` on the PR-ready ones; pick up the stopped issues.
Important
-
Run from the target app repo, never dotfiles — the worktree isolation depends on it.
-
Default is --to pr for the whole batch. Only issues named in --merge can merge, and only through /autopilot's narrow-class gate; a mislabeled one degrades to --to pr inside its own run.
-
Every autonomous merge is Fable-decided — --merge issues are Fable-built, so the merge go/no-go is Fable. Every --to pr build is reviewed at Opus or above, never below itself (Step 4), before it reaches you.
-
The model: label sets the build tier only. The review tier is derived from the build (Opus floor, never below the build) and the merge floor is Fable, so no label can cheapen either — the safety net that catches a cheap build's mistakes is never itself cheapened. An issue with no label runs on the rubric exactly as before, which is what keeps unadopted repos working unchanged.
-
One stop never halts the batch. A stopped issue is set aside with its label intact; the rest continue. A wrong merge is the only truly bad outcome, and the gates above prevent it.
-
Compose, don't re-implement. Build subagents run the real /autopilot; review subagents run the built-in review <PR>. This skill only orchestrates, gates the model floor, and manages the queue label and worktrees — so improvements to those skills flow through untouched. /code-review is not an option here — it is a user-triggered command for a working diff and is not model-invocable, which is why the reviewer takes the PR-number variant. A subagent that finds a skill uninvocable must say so, never substitute a hand-rolled review that reports as the real one.
-
Agents never type the isolation variables — the Step 2 wrapper carries them. /tmp/autopilot-i<n> is the only test/CI/database command an agent runs (prepare, then ci and test), and it is the single enforcement point for both settings below. The build agent runs … prepare as its first action, inside its own worktree; the orchestrator cannot, because the wrapper's worktree guard correctly refuses to run from the main checkout.
-
Why the database must be isolated (RAILS_ENV=test DATABASE_URL=… per agent). Several worktree agents run CI at once; if they all share the project's one default test DB they deadlock against each other — and against any session using that same DB (a common case: a second Claude working in the primary checkout). does solve this — it only fixes fork-starvation one run. Each agent gets a name distinct from the project's default test DB, e.g. (), reused by that issue's gating review. only maps to the connection under . Omit it and silently falls through to the shared default test DB (clobbering the other session), while 's setup runs in — creating the isolated DB stamped and seeding it, which then breaks data-counting tests and makes abort with . Batch-scoped: standalone runs (one worktree, no contention) don't need it.