subagent-driven-development: the ledger records no in-flight task, so an outage mid-task re-dispatches work that is already committed
Found while setting up a sequential subagent-driven-development run (2026-09-12), reading the skill text before dispatching Task 1. Filed rather than patched locally because the plugin is version-pinned.
Version: superpowers 6.1.1. Quotes below are from that copy of skills/subagent-driven-development/SKILL.md.
Related but, I believe, distinct — close as duplicate if you read them as the same thing:
- #2267 — the ledger carries no cross-task discoveries. That is about knowledge lost from completed tasks; this is about there being no record of an in-flight one.
- #2282 — dispatch preconditions are ungated. Its proposal item 3 is "BASE: record
git rev-parse HEAD" — recorded, but only in the controller's context. This issue is that the recording has nowhere durable to go. - #1936 (closed) — ledger carries no plan identity. Different axis.
The gap
Durable Progress gives the ledger exactly one line format, written at exactly one moment:
When a task's review comes back clean, append one line to the ledger […]
Task N: complete (commits <base7>..<head7>, review clean)
And one resume rule:
Tasks listed there as complete are DONE — do not re-dispatch them; resume at the first task not marked complete.
So the ledger is a record of finished work only. The window between dispatching an implementer and its review coming back clean — which is where essentially all of the wall-clock time lives — writes nothing. A task interrupted in that window is indistinguishable from a task never started.
That matters because the resume rule's instruction for such a task is to re-dispatch it, and by then the implementer may already have committed.
Concrete shape of the failure
Sequential SDD, outage (usage limit, crash, Ctrl-C, laptop sleep) during Task 5:
- Controller records BASE in context, dispatches the Task 5 implementer.
- Implementer writes tests, implements, commits, reports DONE.
- Session dies before the review package is generated.
- New controller reads the ledger. Last line is
Task 4: complete. Task 5 is not marked complete, so per the resume rule it is not DONE. - Controller re-dispatches Task 5 against a tree that already contains Task 5's work.
The re-dispatched implementer finds its tests already present and passing. Best case it reports DONE having done nothing, and the task is reviewed properly by luck. Worst case it "fixes" the discrepancy — rewrites the tests it thinks it should have written, or commits a second, divergent implementation on top of the first.
There is a second failure in the same window that is quieter. BASE lived only in the dead controller's context. The skill is explicit about why that matters:
BASE is the commit you recorded before dispatching the implementer — never
HEAD~1, which silently drops all but the last commit of a multi-commit task
A resuming controller has no BASE and no sanctioned way to recover one — so the natural move is precisely the HEAD~1 the skill warns against, and the reviewer silently sees only the last commit of the task.
Why the existing artifacts do not close it
git log— the skill's stated recovery path ("recover fromgit log") shows commits but not which task they belong to, and not whether the task's review ever ran. A task that committed and was never reviewed looks exactly like one that committed and passed.- Report files (
…/task-N-report.md) are written by the implementer, so their presence does indicate a dispatch happened — but nothing in the skill tells a resuming controller to look for them, and a task interrupted before the implementer wrote its report leaves none while still possibly having commits. - Todos do not survive the session, which is the premise of Durable Progress in the first place.
- #2282's BASE precondition records the right value at the right moment; it just has no durable home. The two changes compose: gate the dispatch on recording BASE, and write it where it survives.
Proposal
Give the ledger a second line format, written at dispatch rather than at completion:
Task 5: dispatched (base a1b2c3d, brief .superpowers/sdd/task-5-brief.md) 2026-09-12T18:40Z
Task 5: complete (commits a1b2c3d..e4f5g6h, review clean)
Then replace the resume rule's binary with a three-state one:
- A task whose last line is
completeis DONE. Do not re-dispatch it.- A task with no line was never started. Dispatch it normally.
- A task whose last line is
dispatchedwas interrupted mid-flight. Do not re-dispatch it blind. Rungit log <base>..HEADusing the base from that line:
- No commits — the implementer landed nothing. Re-dispatch normally.
- Commits present — the work exists but was never reviewed. Do not re-implement. Generate the review package with the recorded base (
scripts/review-package <base> HEAD) and dispatch the task reviewer, entering the normal review loop at that point.
The dispatched line is also what makes the "never HEAD~1" rule survivable: BASE stops being a fact only the live controller holds.
Two notes from writing this against 6.1.1:
- The line is append-only and the two formats are distinguishable by keyword, so no existing ledger needs migrating — a ledger with only
completelines behaves exactly as it does today. git clean -fdxstill destroys the ledger, as the skill already acknowledges. This proposal does not fix that, but it does mean the common case (outage, ledger intact) stops requiring the controller to guess.
Scope note
I have not run the failure end-to-end — this is a design gap read off the skill text and the resume rule, not a postmortem. What I can state is that the 6.1.1 skill contains no instruction that writes anything to the ledger before completion, and that its resume rule sends an interrupted-but-committed task back through implementation.
Happy to send a PR — the change is a line format, a three-state resume rule, and one sentence in the DONE handling, all within SKILL.md.
Source: obra/superpowers