implement-loop: a task's git checkout -- <file> recovery silently discards earlier tasks' uncommitted edits to that file
#23
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What happened
During an
implement-looprun (standard mode, a 5-task plan for a large deletioncut — swap three call sites, rewrite/delete tests, then delete a function and its
transitive closure within one crate), the workflow returned PARTIAL: Tasks 1–3
reported DONE, Task 4 BLOCKED.
The final working tree did not match those per-task reports. The edits Task 1
and Task 2 had made to
crates/aura-cli/src/main.rs(the call-site swaps and thetest rewrites/deletions) were absent from the tree, although both tasks
reported DONE. Edits made by Task 3 to other files (a separate integration-test
file, two deleted fixtures, three more test files) were present. Task 4 then
blocked because the call sites Task 1 was supposed to have swapped were still in
their original form, so the function being deleted still had live callers — a
report-versus-tree inconsistency that only surfaced on the orchestrator's own
post-run diff inspection.
Evidence
The run's agent transcript contains a working-tree file checkout issued by an
agent:
git checkout -- <file>(equivalentlygit restore <file>) discards alluncommitted edits to that file, not only the issuing task's own. Because every
task in this run wrote to the same
main.rsand the loop never commits betweentasks, that one command threw away the
main.rsedits accumulated by the earliertasks in the same run. Files other than
main.rswere untouched by the checkout,which is why Task 3's cross-file edits survived while Task 1/2's same-file edits
did not — producing both the tree/report mismatch and the downstream block. The
most likely trigger is an implementer recovering from an intermediate
non-compiling state during the atomic deletion by resetting the file it was
editing.
Where in the plugin
implement/SKILL.md— the Iron Law forbidsgit reset/git revertonmainand states the loop "only ever writes unstaged changes", but does notaddress a working-tree
git checkout -- <file>/git restore <file>thatdiscards a sibling task's uncommitted work in a file several tasks share.
agents/implementer.md— the per-task implementer agent that ran the checkout.The report claimed the two earlier tasks were DONE; only a manual diff of the
working tree against the pre-run commit revealed their edits were gone.
Triage verification at commit
dacadba. The observed behaviour is consistent with the current plugin text, with one sharpening correction: the implementer is handed the discard tool explicitly.Confirmed — no guard exists at the failure's granularity. The Iron Law (
implement/SKILL.md:100-101) covers reset/revert and the no-commit discipline only; the file's othergit checkout -- .mentions (implement/SKILL.md:256,:297) are orchestrator-level whole-tree disposition after the loop ends.implement/workflows/implement-loop.jshas no per-task snapshot or post-task presence check:diff_fingerprint(schema atimplement-loop.js:201-208, loop check at:528-537) hashes the fullgit diff HEADbut only detects a same-task no-progress cycle, and the end-of-iteration tree gate counts changed files — a file whose earlier-task content was wiped still counts as changed, so the wipe is invisible to it.Correction to "contains no guidance mentioning checkout".
implement/agents/implementer.md:163-166(Step 8, self-review) sanctionsgit checkout -- <path>for undoing the agent's own out-of-task-text edits — with no caution that the loop never commits between tasks, so a shared file may hold earlier tasks' already-reviewed DONE work. The in-loop reviewers cannot catch the loss either: both are scoped to the current task's footprint, with chunks outside it declared not their concern (implement/agents/spec-reviewer.md:147-150,implement/agents/quality-reviewer.md:67and:141-142). The failure class is therefore not merely un-forbidden but structurally invisible to every in-loop gate; only a post-run tree-versus-report diff surfaces it, which matches how this instance was found.Two fix layers separate cleanly:
Design resolution for the mechanical-guard layer left open in the triage (fix layer (b) — the prose layer (a) followed directly from the report and needed no decision).
git checkout --/git restorefor loop sessions) vs. a guard inside the plugin's own loop script. → Plugin-side only. Basis: the change must stay within the skills project; a global harness manipulation is ruled out — user decision, 2026-07-09.implement/workflows/implement-loop.js. Basis: derived — with prevention excluded per the fork above, detection needs a recovery anchor to be worth its cost:git stash createyields a dangling commit per task boundary without touching HEAD, index, or working tree (Iron-Law-compatible: nothing commits, nothing moves), so any loss is both detectable and reconstructable (git show <sha>:<path>).git diff HEAD --name-onlyat boundary N+1 trips the guard. Basis: derived — later tasks legitimately rewrite earlier tasks' edits in shared files (the observed run's plan did exactly that), so hunk-persistence semantics false-positive on plan-intended rewrites; the recovery snapshot lowers the cost of the coarse detector's known false negative (a checkout followed by re-edits of the same file escapes the comparison but stays diagnosable post-hoc). Untracked files sit outside the threat model:git checkout -- <path>cannot discard them.BLOCKED, loop stops at the tripped boundary. Basis: derived — a tripped guard means the per-task DONE reports and the tree have diverged, so neither DONE nor PARTIAL is a trustworthy commit signal; the end-report names the lost paths, the boundary, and the recovery snapshot sha.