feature-workflow · diff
git:20260918.f2b0b85 to git:20260921.8b27b40
3 added, 2 removed. Audit A to A.
---
name: feature-workflow
description: The user's six-stage feature pipeline (discuss → plan in native plan mode → validate + approve → delegated execute → one codex challenge per feature → ship), the parallel multi-agent mechanism picker, and the token-discipline rules for long-running agents. Invoke BEFORE starting any non-trivial feature — multiple steps, or work that benefits from a formal review cycle — and before any parallel/multi-agent fan-out. One-shot edits and small fixes outside an active pipeline skip it.
---
# Feature workflow
For pipeline-path work (global CLAUDE.md → Feature workflow): a design/product/UI choice, irreversible, or a real multi-step arc. One-shot work (no design choice, reversible, no plan needed — any file count) skips this skill entirely — but not the codex cross-review: it still runs one `codex-challenge.sh <pre-change-sha>..HEAD` run on its diff under the stage-5 rules before the change counts as done.
One **master session** owns the feature end-to-end. It stays thin by running every token-heavy stage **out of main context** — in a subagent or an agent-team agent. The master coordinates, authors the plan, and ingests summaries; it does not implement. Plan files live in the repo at `docs/prompts/<feature>-plan.md`.
Six stages, each delegated out of main context by the master session:
1. **Discuss approach.** Explore the problem space, surface constraints, decide what's worth building. No code.
- 2. **Plan in native plan mode** → the master calls `EnterPlanMode`, then authors the plan itself, writing directly into the plan file named in the plan-mode system message — that file is the canonical plan until approval. Codebase discovery goes to `explorer` subagents (Sonnet, read-only), which return summaries; the master reads no product file itself in plan mode — a hard line in global CLAUDE.md, because exploration done in the master's own context is re-paid by every later turn of the arc. Revisions — reviewer blocking findings, user answers, ExitPlanMode "keep planning" feedback — are the master's own edits of the plan file, not a fresh spawn.
+ 2. **Plan in native plan mode** → the master calls `EnterPlanMode`, then authors the plan itself, writing directly into the plan file named in the plan-mode system message — that file is the canonical plan until approval. Write it once; every later change is an `Edit` of the section that changed, never a whole-file rewrite — each rewrite re-enters the full plan into the master's context. Codebase discovery goes to `explorer` subagents (Sonnet, read-only), which return summaries; the master reads no product file itself in plan mode — a hard line in global CLAUDE.md, because exploration done in the master's own context is re-paid by every later turn of the arc. Revisions — reviewer blocking findings, user answers, ExitPlanMode "keep planning" feedback — are the master's own edits of the plan file, not a fresh spawn.
**Plan shape.** The plan states: the goal; the ordered steps, or for a parallel run the independent units (file/ownership boundaries, the shared contracts units must agree on, and a check that they are genuinely independent — otherwise say the work is sequential); every execution step names the subagent that executes it (`step-executor` for sequential steps, `team-executor` for parallel units) — never the master; steps sized so an executor finishes in roughly ≤100 tool calls — a step that edits or creates 7+ files, or sweeps existing call sites while adding behavior, is over it — split otherwise; the arc sized to one review gate — more than 5 execution steps, or steps that together touch 40+ files, is two arcs, each with its own stage 5 and a device/client pass between, and the plan names the checkpoint; acceptance criteria stated once per step; edge cases; for every existing identifier whose meaning or scope the plan changes, the explorer-produced list of its call sites (file:line) inside the step that changes it — the executor works a checklist, not a search, because a sweep run from search misses sites and each miss is a stage-5 finding; for every new mechanism (a parser, a cache, a transform, an undo basis), one line naming the invariant that must hold and its bound — max size, count, or memory, and what happens past it — because a mechanism the plan leaves unbounded is the shape of every non-convergent fix loop measured so far, fixers patching the trigger round after round; a verification step that drives the positive path through the real client (`/browse` for web, a simulator or device render for iOS, the built routes for a web build) — curl and unit suites prove only the negative path; per-step verification is the build plus the targeted suites the step touches, with the full suite run once as its own final step; any step marked Opus carries a one-line reason; and a taste/open-decisions list, each item with a recommended option and the simplest option, never decided silently.
3. **Validate + approve** → spawn `team-plan-reviewer` (no `model:` — it inherits the session's model; read-only, one pass) with the plan-file path; it checks the plan against the code and returns `blocking` / `advisory` findings. Blocking → the master revises the plan file → done (no re-review unless the revision was material; never more than two review passes). Advisory → reported to the user at the gate, never looped on. Then resolve the plan's taste/open decisions with **one** AskUserQuestion (per the Plan gate cap in global CLAUDE.md), edit the answers in, and call `ExitPlanMode` — Claude's native approval gate. Delegated approval (the owner said "approve it yourself") follows the AFK gate's two branches in global CLAUDE.md: not yet in plan mode → skip it, plan file straight to `docs/prompts/`, reviewer still runs; already in plan mode → say in one line that only the owner's click or Shift+Tab leaves plan mode, before they go. "No, keep planning" feedback goes back to the master's own revision of the plan file and through `ExitPlanMode` again; on approval the master copies the plan file to `docs/prompts/<feature>-plan.md` and commits it where the project tracks `docs/prompts/`, records the feature-base sha (`git rev-parse HEAD`) for the stage-5 range, and mirrors the plan into the native **task list** (`TaskCreate`): one task per execution step plus the fixed tail — codex verdict clean or round 2 final with the remainder deferred; standalone-P2/test-gap/theoretical fix-or-defer asked and tech-debt entries committed; test suite exits 0 with output shown; handoff written; no live agents. Chain them with `TaskUpdate addBlockedBy` where a step actually consumes an earlier one's output — it edits a file that step created, builds on a decision it made, or verifies its work; steps that need nothing from each other stay unblocked. The codex task is blocked by every step, the fix-or-defer task by codex, tests/handoff/no-live-agents by everything before. The plan doc stays the source; the task list is its live summary (`Ctrl+T`). Then start stage 4 unprompted.
4. **Execute** → delegate each step to a subagent; commit per step. Spawn executors **unnamed** — `name:` reroutes delivery to the teammate mailbox, an extra hop an executor has no use for; unnamed spawns auto-deliver their final report. Sequential steps go to `step-executor` (session's own branch, no worktree — it's the only writer in flight); independent steps that run at the same time go to `agent-teams` and its `team-executor` instead. State each step's acceptance criteria once and stop there — no "re-verify", "double-check", or per-round verification rituals in spawn prompts. Size and split spawns here, not later: a plan step is sized so a step-executor finishes in roughly ≤100 tool calls (split it in the plan if it can't), and every spawn carries exactly one concern — one step, or one fix. Never bundle several playtest fixes, regressions, or finding sets into a single spawn; a fixer gets the plan section, the diff range, and the one finding set it is fixing. **Fixing reported-broken behavior is red-then-green, test-first:** the test is written and run red *before* the fix is applied — one red run, then the fix, one green run, both shown. Reverting a finished fix to show the test red is the fallback, not the default. This is an acceptance criterion, not a verification ritual. It applies to fixers and to any step whose job is a stated bug; a step that builds new behavior just states its criteria. **Test cadence:** a step's acceptance criteria name the build and the targeted suites that cover it; the whole test suite runs once after the last step lands (the master schedules it as the final execution task) and again after the fix loop — never per step. Test evidence from any agent — executor, fixer, or a base-comparison spawn — is the runner's executed/skipped/failed counts, never just the absence of failures: a suite whose environment gate skips silently reads green. **Per-step review:** round N launches in the same turn step N's commit lands, range `<previous round's head>..<HEAD now>` (round 1's base = the feature base) — contiguous by construction, since every commit between two rounds, including fixer commits, lands in exactly one round and the rounds jointly cover the range with no gap; the last step gets no per-step round, stage 5 covers it. Always `--pin` (a later executor may be writing) — the script creates and removes its own worktree, the master never adds or removes worktrees itself. Verbatim call: `~/.claude/skills/feature-workflow/scripts/codex-challenge.sh <base>..<head> --pin --trace --out <scratchpad>/codex-step<N>.md` as one `run_in_background` Bash whose `description` names the round and range — `Codex challenge step <N> · <base>..<head>` — since that label is all the task list shows of a background shell; the master ends its turn or does work not depending on the verdict. On completion: one `codex-triage` spawn — file path(s), range, head sha, verified with `git show <head>:<path>` / `git diff <base> <head>` — and P0/P1 findings go to one `fixer` before the next executor that depends on the reviewed step spawns (fixer and executor are both single-writer, so the fixer goes first and the following round's range contains the fix). The step boundary is dependency-aware, using the task list the stage-3 mirror already builds (`addBlockedBy` where a step consumes an earlier step's output): an executor spawns only when every step it is blocked by has its per-step verdict in and its P0/P1 fixed; a step blocked by nothing pending spawns immediately, and its round launches in the same turn; concurrent read-only runs are fine, each pinned at its own head. Stage 5 launches only when no per-step round is in flight and no fixer is pending — a fixer commit after the whole-range launch invalidates it. Per-step rounds flag correctness and requirement gaps only — no style, no speculative hardening — and they are bug-catching, not the gate: stage 5's whole-range challenge still decides clean. **Model:** the global spawn-pin rule applies. Its two pipeline exceptions: Opus for an executor when the plan marks that step Opus with a one-line reason and the plan-reviewer let it stand, and Opus for a fixer only in the same-mechanism structural case below. Every other fixer is Sonnet, and fixers spawn as `fixer` (effort medium), not `step-executor` (effort high). **A returned report's open question is the owner's, not yours to resolve.** When an agent's report asks something — "say the word if you want it mirrored" — that question goes to the next gate verbatim, as its own AskUserQuestion or an explicit option, never replaced by the master's framing of the same decision. Same care in reverse: the owner can type directly into a running agent's chat and that message reaches only that agent, so an unexplained diff from a live agent is a question for the agent — read its transcript — not a rogue-agent finding. **Task list:** mark a step's task `in_progress` when its executor is spawned and `completed` when its commit lands and acceptance criteria are shown (`TaskUpdate`) — never completed with failing tests or a partial fix; a blocker becomes a new task, not a silent skip; a task is deferred only with the user's say-so, with the reason in the task. The arc is complete when no task is open, and the closing report shows the final `TaskList`.
5. **Independent review per feature** → after the last step is committed, ONE codex challenge on the feature's whole diff, where `<feature-base-sha>` is the commit the first step started from (recorded at plan approval). The master launches `~/.claude/skills/feature-workflow/scripts/codex-challenge.sh <feature-base-sha>..HEAD --trace --out <scratchpad>/codex-whole-round<N>.md` as one `run_in_background` Bash described `Codex challenge round <N> · <feature-base-sha>..<head>` — no `--pin` (nothing writes during the gate; the fix loop is serial: challenge → fixer → re-challenge on the same command with the new HEAD, each round under its own `--out` name — `-round<N>` per the Verdict size contract's naming, never reusing a prior round's file, since the script truncates `$out` on each run and a discarded round's standalone findings would otherwise be lost before the fix-or-defer question below) — and spawns the kit's `codex-triage` agent on the completion notification — never a subagent for the run itself (why: Token discipline). Stage-4 per-step rounds and fixer-commit deltas never satisfy this gate — the gate is the whole range at a single HEAD; never `codex:codex-rescue`; a pass/fail codex review only when the user asks for it by name. In parallel with the whole-range run, one `spec-reviewer` spawn (Sonnet, read-only) checks `<feature-base-sha>...HEAD` against the approved plan file — unimplemented or incomplete requirements, scope creep, wrong-logic-vs-spec; gaps only, no style. A missing requirement routes into the P0/P1 lane; scope creep is reported at the gate. Triage real / regression / test-gap / theoretical, and give every real finding a priority: **P0** = crash, data loss, security, or a regression breaking a core flow — fixed immediately, and always blocks ship; **P1** = wrong behavior reachable in normal use (any other regression is at least P1, never P2); **P2** = real but reachable only through unusual or pathological input; test-gap and theoretical carry no priority — priority exists to route the fix loop, and neither class ever enters it. Every finding also carries the runner's own confidence, 0–1 — a finding the runner half-believes is a `conf:0.4` line, never a paragraph arguing itself down. **Clean = zero P0/P1.** Fix loop: the loop runs on P0/P1 only — one fresh Sonnet fixer per finding set → re-challenge — and each set also picks up any P2 sharing a file or mechanism with its P0/P1s. **Two rounds, then stop:** round 1's P0/P1 go to fixers — a set whose findings are instances of one mechanism goes to one mechanism-fixer (the same-mechanism clause, Token discipline), never per-path patches; round 2 re-challenges the resulting HEAD and its verdict is final. There is no round 3: a large surface returns a fresh finding set every round, so the count never converges and a third round buys hours, not a clean verdict. **A contract the plan did not change never changes from the fix loop**, whatever the finding's priority: a wire schema version, an API field, a persisted data model, anything a server or a second client must also honor. That gate's options are *defer to its own arc* (a P0 blocks ship until that arc lands; a P1 ships with its index line) or *ship as-is* — never *fix here*: a contract change built by a fixer skips the plan and the plan-reviewer, and every later round then reviews the unplanned design one piece at a time. After round 2, an open P1 is deferred with an index line (the tech-debt path below) and named at the push gate; an open P0 goes to one AskUserQuestion (structural fix in its own arc, or defer — a P0 never ships), never silently to stage 6 — P2 and below are deferred with the report. Standalone P2, test-gap and theoretical findings are never looped on: once the verdict is clean or stopped, ask ONE AskUserQuestion listing the union of standalone findings across every round's verdict file (a round-1 finding round 2 does not repeat still belongs on the list — each round's `--out` file survives, per the per-round naming above) — *fix now* (one fixer spawn for the set) / *defer to the project's tech-debt file* / *let me pick* — where deferring copies that round's verdict file from the scratchpad to `docs/reviews/<feature>/<round>.md`, writes one **index line** per deferred finding — `[P2 conf:0.6] file:line — summary → docs/reviews/<feature>/<round>.md #finding-N` — to the project's index (`TODOS.md`, or `docs/tech-debt.md` if none), and commits both with the feature; a round with nothing deferred leaves no verdict file in the repo. A feature with no repo diff needs no verdict.
**Tell the runner to trace out of the diff.** The prompt says to follow the diff into the state machines, invariants and shared components it perturbs without changing their lines, and to report findings there. This is an instruction to the reviewer, not a list the master enumerates in advance.
**A range too big for one prompt is split, and the slices are still one gate.** "ONE challenge" above is about coverage and cadence — one gate per feature over the whole range — not about one process doing it. The trigger is behavioral — the run times out, truncates, or the runner reports it could not read the whole range — not a byte threshold. Split into contiguous commit sub-ranges so the slices **jointly cover the whole range with no gap**, run one `codex-challenge.sh` call per slice, concurrent, **unpinned** (no writer at stage 5, and an unpinned slice reads the final tree, so out-of-diff tracing sees the finished code), and hand ALL slice output files to a single `codex-triage` spawn, which merges and dedupes them into the one triaged verdict — the master never triages slices separately or hand-recomputes cross-slice dedup. The coverage claim is only as good as the gaplessness, which is by construction here, and each slice's range is recorded in its own header line; overlapping slices are fine, a gap is a failed gate. A single commit too large to review on its own can't be split into sub-ranges — relaunch once unsplit; if it stalls again, review by path is a script addition (`--paths`), deferred to tech-debt. Record the reviewed range in the verdict file — `<base-sha>..<head-sha>` — the next session cannot otherwise tell what has already been gated.
**Failure paths.** Exit 124 → relaunch once with the same arguments, under a different `--out` name (e.g. a `-r2` suffix). A second 124 → the range is too big: split it (above) or, when this fires inside a stage-4 per-step round, skip that round and note it in the cost checkpoint — stage 5 still covers the skipped range. Non-zero after the script's three attempts, or exit 0 in under 60 s with no findings text in the verdict body (auth or usage-limit failure — triage reports it verbatim) → park: push-notify, end the turn, no relaunch — the script already retried for 10 minutes. Exit 66 (pin refused, under the free-space threshold) → park like the other failures: push-notify, end the turn, no unpinned retry.
**Brief the defect and the invariant, never the patch.** A fixer's spawn prompt carries what is broken and what must hold afterward. If the master has a fix in mind it goes in labeled as a hypothesis to verify and overrule, because the fixer is the one who will read the call sites.
6. **Ship** → push (owner-approved) → PR → merge + deploy → post-deploy verify.
Rules:
- The always-on hard gates in global CLAUDE.md apply throughout: push approval, the codex gate, verified-claims, AFK-is-not-approval.
- **The master never touches product code** — the global hard gate, in force from the moment this skill loads.
- **A missing report is a read-the-transcript signal, never a wait.** An API error that ends a subagent early is never delivered as its result — the master gets nothing. If a delegate goes quiet past its expected window, read its transcript under the session's `subagents/` dir: the work is often already complete. Salvage it, and respawn a fresh agent for only the remainder.
- **Interactive gates never go to subagents.** Subagents are headless — they cannot call `AskUserQuestion` or `ExitPlanMode`, so a delegated gate either auto-picks silently or dies. Gates run in the master session; only headless work (drafting, reviewing, executing) is delegated.
- **Plan-approval is the trigger, not a suggestion.** The moment build approval lands, transition unprompted into the delegated tail — spawn the first executor in the same turn and run stages 4–6 to completion without returning to the user except at the real gates (a structural fix exceeding the plan's design contract, the standalone-P2/test-gap/theoretical question, push approval).
- - If the master nevertheless approaches the context ceiling (~500k tokens; 600k absolute max), do a **deliberate, user-assisted handoff** to a fresh master session — don't silently push past it. `/context-save` + `/context-restore` are the bridge.
+ - **A new arc gets a new master.** When an arc completes and a next one is queued, the master writes the next arc's brief to `<scratchpad>/handoff-<feature>.md` — goal, plan pointers, feature-base sha, what landed and what was deferred, the owner's standing instructions verbatim, never push — and hands off. Attended: `/context-save` + `/context-restore`, the owner pastes. Unattended, under delegated approval: launch it — `cd <repo> && claude --bg --name <feature> --permission-mode auto "$(cat <scratchpad>/handoff-<feature>.md)"` as one foreground Bash — then end this session. A `--bg` session is a full session (hooks, skills, agents and background-Bash wake-ups all work), outlives the session that launched it, and is listed by `claude agents`; an `AskUserQuestion` inside it parks the session under "Needs input" until someone attaches, so the brief must carry the delegation that makes gates self-resolving. The same handoff applies if the master approaches the context ceiling mid-arc (~500k tokens; 600k absolute max) — never silently push past it.
- **Master budget: a normal feature arc finishes under ~400k with no compression machinery.** Crossing ~400k mid-arc is a *defect signal*, not a reason to compress or hand off: stop, post the cost checkpoint, and name what's flooding the context so the flow gets fixed. During the unattended tail, post a one-line cost checkpoint at every step boundary (elapsed time, approx context size), and END the session the moment the arc completes — never leave a finished session idling.
- Make handoff artifacts cold-start-ready *without being asked*: one standalone root README a fresh session needs no other file for, and attach plans to their task/ticket so a future session finds them by reference.
- Plan reviews used individually (`/plan-eng-review` etc.) run via sub-agents — review token burn doesn't belong in main context.
- If work is interrupted mid-step, commit `WIP:` so a resume is clean.
# Parallel multi-agent
For genuinely **parallel, independent** work only; sequential pipelines belong to the feature workflow above. Pick the mechanism by need:
- **Background subagents (DEFAULT):** independent units, contracts known up front. Concurrent writers each need a worktree — spawn them as `team-executor`, which carries `isolation: worktree` in its frontmatter so no spawn call has to remember. Read-only fan-out needs no worktree. Set `worktree.baseRef: "head"` first, or executor worktrees branch from the remote default branch instead of your in-progress work.
- **Workflows:** large (10s+), deterministic/repeatable/resumable fan-outs with cross-checking.
- **Kill delegated agents the moment their unit closes.** Once an agent's output is ingested (commit merged, verdict triaged, report received), stop it in the same turn — agents are per-unit disposables, never kept warm. A result marked partial at `maxTurns` is not an ingested output: that unit is still open, and the agent is resumed, not replaced. Before declaring an arc complete, enumerate live agents and confirm zero remain; the user must never have to kill leftovers by hand. "Zero" also means `pgrep -f 'output\.done'` returns nothing (a leftover marker-file poll is the one shell shape a subagent can orphan; bare `sleep`/`codex` patterns match the owner's own processes and are not criteria) and `ListAgents` shows no subagent still `running`. `TaskStop` is for an agent still running when its unit closes; one whose completion notification has arrived is already gone from the task registry — `ListAgents` no longer shows it and `TaskStop` returns "No task found".
- **When to offer (lead only):** if a task has independent parallel parts AND the user hasn't specified the approach AND the project's CLAUDE.md hasn't set a preference → ask whether to fan out before starting. If you are a worker, never re-ask — just do your assigned task.
- **How:** invoke the `agent-teams` skill for the full playbook (mechanism choice, roles, models, worktree/merge flow, the plan-only approval gate). Don't inline the playbook here.
# Token discipline
Cost ≈ turn-count × context size: every agent turn re-pays its entire context as cache-read, so burn grows quadratically in a long-lived agent. Agent *lifetime* is the lever — not result size or output verbosity.
- **Retirement is mechanical: `maxTurns` in the agent frontmatter, not a prose budget.** `step-executor`/`team-executor` cap at 200 turns, `fixer` at 150, `codex-triage` at 60; each agent's own instructions tell it to commit `WIP:` once it passes ~three-quarters of its cap and keep working. When the cap cuts an agent mid-flight its result comes back marked partial: the master resumes it with `SendMessage` (full history intact, no ramp-up), and only if the resumed agent caps again reads its transcript under `subagents/`, commits any `WIP:` itself, and respawns a fresh agent for the remainder — it never raises the cap, and spawn prompts carry no budget line.
- **The codex run lives in the master, backgrounded; a fresh `codex-triage` agent per round.** A challenge on a real feature range takes 3–8 minutes of `codex exec` on gpt-5.6-sol/medium (measured 2026-09-16, 12 runs), with a 15-minute stall ceiling equal to the 900 s Bash cap (`BASH_DEFAULT_TIMEOUT_MS`, set by the kit's settings). A subagent cannot wait that out: a foreground compound pipeline is *stopped* at the cap, not moved to the background (only simple commands auto-background), and a subagent's backgrounded call keeps running past its final response — the verdict lands in a context that no longer exists — so the kit's PreToolUse hook `hooks/subagent-no-background.sh` denies `run_in_background` inside any subagent. The master is the one context the harness re-wakes on a background completion, so the run is `codex-challenge.sh` as one `run_in_background` Bash in the master — the script carries `gtimeout 900`, the 3-attempt/5-min retry, the optional pin, and writes the output file. After launching, the master ends its turn or does work that doesn't depend on the verdict. Triage is a fresh `codex-triage` spawn (Sonnet, medium) per round: it reads the round's output file(s) — all slices of a split round go to the one spawn — checks each finding against `git show <head>:<path>`, and returns the ≤2,000-char verdict; a round needs only the range and the file(s), never prior rounds' context. Post-review fixes go to a fresh `fixer` agent (Sonnet at effort medium, frontmatter default) that commits its fix on the branch like any executor, never back to the original executor at peak context. A fixer spawn prompt is briefed like an executor's: the plan section (path + line range), the diff range, and the verdict's P0/P1 findings (plus their adjacent P2s) only.
- **Long runs are backgrounded with an explicit `gtimeout`, never unbounded.** A full test suite runs as one `run_in_background` Bash with `gtimeout 3600` ahead of the command, under the same retry wrapper as the codex run.
+ - **Build and download logs are pre-filtered before `tail`.** `xcodebuild`, platform downloads and similar tools write progress as `\r`-joined runs, so `tail -n 8` can return one line tens of thousands of characters long. Pipe through `tr '\r' '\n' | tail -n 20 | cut -c1-300`, or `grep` for the lines that matter.
- **An unpinned challenge really is coupled to the working tree — pin it or serialize.** Challenge mode does not hand codex a precomputed diff the way review mode does: it passes a prompt telling codex to work out the diff itself with git, then trace the code, all inside a read-only `codex exec`. So the diff *and* the reads both come from the live tree, and a concurrent fixer corrupts both. `--pin` does the free-space check, the stale-worktree sweep, the detached worktree, and its removal on exit — which is how `codex-challenge.sh` gives an overlapping run its own checkout. Two limits on what that buys: a verdict pinned at sha X says nothing about a commit landing after X, so overlapping a review with unrelated fix work surfaces bugs **earlier** but does not satisfy the gate — stage 5's clean verdict is still one challenge over the final range at a single HEAD — and the pinned sha must stay downstream of the recorded feature base, a history rewrite past that base fails as the script's exit 65. In practice: a stage-4 per-step round always pins (the next executor is writing); a review launched with no writer in flight runs on the live tree, no worktree at all. Every pin is preceded by a free-space check (`df`, ≥2 GB on the scratch volume) that fails loudly instead of burning the launch, and the worktree is removed on exit — never accumulated.
- **Fixers stay serial by default.** `fixer` is single-writer by definition (session branch, no worktree), so writer fan-out is a different agent, not a fixer flag: if a round's finding sets genuinely touch non-intersecting files, that is a `team-executor` fan-out under the `agent-teams` skill. Disjoint file lists are the entry condition, not the proof — a fix can reach past its finding's location, and two fixes can collide semantically in files neither list named — so treat an intersection as a hard no and a non-intersection as worth a second look at what each fix actually has to change.
- **Same mechanism twice = stop patching paths.** If a round's findings are instances of one mechanism, or hit the mechanism the previous round just fixed, the loop is chasing instances, not the cause. Say so in the round's report, and make the mechanism the fixer's brief — its spawn prompt names the invariant to establish and the now-redundant per-path patches to remove, so two mechanisms don't end up stacked. If the structural fix is materially larger than the plan's design contract anticipated, that is an AskUserQuestion (structural fix / patch this path and carry the rest), not a judgment call. Opus for a fixer only here, with a one-line reason stated at the spawn. In stage 5 this is round 1's fixer: a round-2 verdict that hits round 1's mechanism exits the loop under the two-round rule, never a third round.
- **Verdict size contract.** Full challenge output is written to a file in the session scratchpad (never a bare `/tmp/codex-*.md` — two masters in one repo collide); it enters the repo only when a finding is deferred (stage 5 above), and the master shows only the triaged verdict ≤2,000 chars plus that file path. Verdict shape: counts per class on the first line, then findings **grouped by class in priority order — real, regression, test-gap, theoretical** — one `### <class>` header per non-empty class, one `file:line — summary` line per finding beneath it, prefixed `[P0 conf:0.9]`/`[P1 conf:0.8]`/`[P2 conf:0.4]` on real and regression lines and `[conf:0.5]` alone on test-gap and theoretical ones (never an interleaved flat list, and no class tag repeated per line: the header carries it). **One line per finding, always** — a finding that needs a paragraph has the wrong class or confidence. The master **recomputes the per-class counts from the finding lines** and uses its own numbers: the first line is the triage agent's claim, not the record, and a first line that disagrees with the lines beneath it is reported as a mismatch next to the corrected counts. If the ≤2,000-char cap forces findings to be dropped, drop only test-gap and theoretical lines and say so inline on the counts line — `test-gap: 7 (2 dropped)`; real and regression lines are never dropped, so the count that decides clean is never silently short. The actor is explicit: the master's background `codex-challenge.sh` call writes the full output to the verdict file (`--out`), and the `codex-triage` spawn prompt names that round's file(s), the range and the head sha. Never paste a verdict in full. The master NEVER reads challenge-output files — not with Read, and not with `head`/`tail`/`cat`/`grep` in Bash, and that includes `$out.log` and `$out.msg` alongside `$out` itself; the triage agent's report is the only channel. The `.log`/`.msg` sidecars next to `--out` are review scratch and never enter the repo.
- **Measuring burn from transcripts: dedupe by requestId/message.id first.** Claude Code writes one JSONL line per content block, each repeating the full request's usage — naive per-line sums overcount 2–3.5×.