Context
Found by the PR #86 final review (adversarially verified, chain confirmed link-by-link). An interaction between #86's restore latch (which supplies the untracked payload) and #78's re-arm baseline machinery (which supplies the absorption). Tracked patch hunks are safe — this is untracked-files-only.
The chain
- A restore re-drive escalates AFTER the patch applied (session error / CRITICAL / exhausted-mid-redrive / pre-commit veto / commit-fail — all PAUSE without a rollback;
_escalate deliberately preserves the tree). Note: review non-convergence is a DEFER path and canNOT feed this chain.
- The human re-resolves and re-arms from scratch (the documented recovery).
rearm_escalation refreshes task.baseline_untracked from the current tree (runs.py ~655-662) — the applied patch's new untracked files are captured as 'pre-existing'.
- Every later rollback preserves them (
safe_rollback subtracts the snapshot), the corrected implementation proceeds around them, and finalize_commit's add -A sweeps the abandoned attempt's files into the corrected story's commit.
- The only remaining defense is upstream step-01's prose dirty-tree check — a probabilistic LLM judgment, exactly the guarantee class the restore design elsewhere refuses to rely on. (Tracked modifications ARE handled: parked on a preserve ref + reset at the resume's rollback.)
Worse variant: commits the escalated re-drive session made sit below the re-arm's refreshed baseline_commit and become the re-drive's permanent starting point — no reset revisits them.
Fix shape
When a re-arm REPLACES a previously-latched restore (the escalated task's old task.restore_patch is non-None), clean the abandoned application first: the old latch names the patch file, so its file list is mechanically recoverable (git apply -R for a clean reverse, or exclude its named new files from the refreshed snapshot / delete them). Alternatively (or additionally) warn at re-arm when the tree carries untracked files that arrived after the original baseline snapshot.
Context
Found by the PR #86 final review (adversarially verified, chain confirmed link-by-link). An interaction between #86's restore latch (which supplies the untracked payload) and #78's re-arm baseline machinery (which supplies the absorption). Tracked patch hunks are safe — this is untracked-files-only.
The chain
_escalatedeliberately preserves the tree). Note: review non-convergence is a DEFER path and canNOT feed this chain.rearm_escalationrefreshestask.baseline_untrackedfrom the current tree (runs.py ~655-662) — the applied patch's new untracked files are captured as 'pre-existing'.safe_rollbacksubtracts the snapshot), the corrected implementation proceeds around them, andfinalize_commit'sadd -Asweeps the abandoned attempt's files into the corrected story's commit.Worse variant: commits the escalated re-drive session made sit below the re-arm's refreshed
baseline_commitand become the re-drive's permanent starting point — no reset revisits them.Fix shape
When a re-arm REPLACES a previously-latched restore (the escalated task's old
task.restore_patchis non-None), clean the abandoned application first: the old latch names the patch file, so its file list is mechanically recoverable (git apply -Rfor a clean reverse, or exclude its named new files from the refreshed snapshot / delete them). Alternatively (or additionally) warn at re-arm when the tree carries untracked files that arrived after the original baseline snapshot.