implement-queue
Implement every ticket in the folder the user names, in order, each in an isolated context. Run to completion. A developer is watching but WILL NOT be asked for anything.
Throughout, $Q is the ticket folder the user named (default tickets/) and
$S is ~/.claude/skills/implement-queue/scripts. Every script below is invoked
from that absolute path — nothing needs to exist in the project except $Q.
THE ONE RULE THAT KEEPS THIS RUN ALIVE
You persist across ~25 iterations. Your context is the scarce resource.
NEVER:
- run the test suite yourself (
verify-ticket.shdoes it) - read, cat, tail, or grep any file in
logs/ - read a diff, a commit, or a source file
- paste ticket contents into a dispatch prompt (pass the PATH; the worker reads it)
- summarize, narrate, or reflect on completed tickets
- hold the ticket list, the graph, or progress in your head
Every command you run returns ONE line. If something returns more, you called the
wrong thing. All state lives in $Q/.queue-state.json. Re-read it via queue.py;
never rely on memory. If your context compacts mid-run, the next queue.py next
call tells you exactly where you are — just keep looping.
The gap between following this section and ignoring it is roughly 16k tokens versus 150k. It is the difference between a run you can watch and a run that compacts at ticket 14.
Phase 0 — Bootstrap (once, cheap)
The project only has to supply tickets. Fill in the rest without asking:
- If
docs/CONVENTIONS.mdis missing: copy~/.claude/skills/implement-queue/assets/CONVENTIONS.template.mdto it, and say in one line that a starter file was created and is worth editing before a real run. - Check for the starter marker — one grep, one line of output, no file reading:
grep -c QUEUE-STARTER-UNEDITED docs/CONVENTIONS.mdIf the count is 1, announce exactly:WARNING :: CONVENTIONS.md is still the unedited starter — expect more BLOCKED ticketsThen continue. Do not stop, do not offer to fill it in, do not ask. The user knows what a warning means, and a generic conventions file degrades quality rather than breaking the run. - If
docs/IMPLEMENTATION_CONTEXT.mdis missing: create it with the single line# Implementation context. - Do not read either file yourself. The workers read them.
Phase 1 — Preflight (abort cheaply)
$S/preflight.sh→ abort on FAIL, report the line, stop. The OK line ends withtest_cmd=<...>. Capture that string; it is the project's detected test command and you pass it on in step 3. Do not detect it yourself.BASE=$(git rev-parse HEAD),BRANCH=$(git rev-parse --abbrev-ref HEAD)python3 $S/queue.py plan "$Q" --base-sha $BASE --branch $BRANCH --test-cmd "<detected>"Non-zero exit means the graph is invalid (missing or forwardblocked_by, duplicate prefix, id/filename mismatch). Report the errors and STOP — do not attempt to fix tickets yourself. Exit 2 means a state file from an earlier run disagrees with the folder; tell the user to rerun with--forceand stop.- Print the plan and the count. Then BEGIN IMMEDIATELY. Do not ask for confirmation; the user already confirmed by asking for the queue to be run.
Phase 2 — The loop
Repeat until queue.py next exits 10:
python3 $S/queue.py next "$Q"exit 10 → queue exhausted, go to Phase 3. Output is<ticket-id><TAB><abs-path>.PREV=$(git rev-parse HEAD)python3 $S/queue.py mark "$Q" <id> running- Announce one line:
=== <id> (attempt 1) - Dispatch the
ticket-implementersubagent via Task. The prompt contains ONLY:
On a retry, addTICKET: <abs-path> TICKET-ID: <id> CONVENTIONS: docs/CONVENTIONS.md CONTEXT-LOG: docs/IMPLEMENTATION_CONTEXT.md BRANCH: <branch> BASE-SHA: <PREV>RETRY-LOG: logs/<id>.verify.log— pass the PATH. Do NOT read that file yourself; a stack trace in your context is hundreds to thousands of tokens at the worst possible moment, and the worker can read it in its own disposable context for free. $S/verify-ticket.sh <id> $PREV "$Q"→ one line, OK or FAIL. Trust THIS, never the subagent's return. The return is a claim; this is evidence.- Branch on the result:
- OK →
python3 $S/queue.py mark "$Q" <id> done --sha <short-sha>; reset the consecutive-failure counter to 0; next iteration. - Subagent returned BLOCKED →
git reset --hard $PREV && git clean -fdq;python3 $S/queue.py mark "$Q" <id> blocked --reason "<the BLOCKED clause>". Do NOT retry a genuine BLOCKED — it is the worker's considered judgment that the ticket is defective, so a retry burns 20k tokens to receive the same answer. Increment consecutive failures. Continue to the next ticket. - FAIL and attempt 1 →
git reset --hard $PREV && git clean -fdq; announce=== <id> (attempt 2); re-dispatch once with RETRY-LOG. Go to 6. A FAIL is often a flake or a half-finished edit, and a second fresh context frequently fixes it. - FAIL and attempt 2 →
git reset --hard $PREV && git clean -fdq;python3 $S/queue.py mark "$Q" <id> blocked --reason "<the FAIL line>". Increment consecutive failures. Continue.
- OK →
Always reset before a retry and after a failure. Attempt 2 starting from attempt 1's wreckage is strictly worse than starting clean — the model spends its fresh context reverse-engineering a mess instead of implementing.
The reset is safe to run in the user's own checkout because preflight refused to start on
a dirty tree: every $PREV is either the base commit or a commit this run made, and
git clean -fdq (no -x) leaves gitignored files alone. Never add -x, and never reset
to anything other than the $PREV you captured in step 2 of this iteration.
queue.py next automatically marks the transitive dependents of any blocked ticket
as skipped. You do not compute this. You do not reason about the graph.
Circuit breaker
Two CONSECUTIVE blocked tickets → STOP the loop and go to Phase 3. One failure is information about a ticket. Two in a row is information about the run — a broken environment, a wrong convention, an unusable base. Burning 20 tickets of tokens against a systemic fault is the expensive mistake here.
Phase 3 — Finalize
- Run the project's test command once, redirecting to a log:
<test_cmd> > logs/final.log 2>&1 && echo GREEN || echo RED(one line — do not read the log). Get<test_cmd>frompython3 $S/queue.py test-cmd "$Q"if you no longer have it. python3 $S/queue.py report "$Q" | tee logs/REPORT.md- Print the report table and stop. If anything is blocked or skipped, say only: "Ask me to triage the queue when you want to work through those." Do not begin triaging.
If the developer interrupts
They may hit Esc and talk to you mid-run. Answer them. When they say continue,
resume Phase 2 from step 1 — queue.py next re-derives position from the state file.
Do not try to reconstruct where you were from the conversation.
Triage mode (only when explicitly asked)
When the user asks to triage or recover a halted run, do not restart the loop. Instead:
python3 $S/queue.py report "$Q"— read the blocked reasons from the table.- For each blocked ticket, state the reason and what decision would unblock it.
Reasons that are questions belong to the user; reasons that are FAIL lines mean
reading
logs/<id>.verify.log— now safe, because the run is over. - Edit the tickets only when the user agrees to the fix.
- Re-running the queue afterwards is
queue.py plan ... --forcefollowed by a normal run; already-done tickets are re-run from scratch, so prefer branching from the last good commit.
Reference
references/ticket-format.md — the exact ticket frontmatter and section layout the
graph validator expects. Read it only if the user asks about ticket structure or if
queue.py plan rejects their files.