fix(delegate): keep the worktree when git inspection fails - #88125
liuhao1024 wants to merge 2 commits into
Conversation
finalize_subagent_worktree() treated a non-zero exit from its rev-list or status probes as proof of the payload defaults (commits=0, clean), then pruned on them: git worktree remove --force plus branch -D permanently deleted a child's uncommitted work whenever git could not inspect the tree (e.g. a corrupted index) (NousResearch#88113). A destructive cleanup now requires affirmative proof of zero commits plus a clean tree. Any non-zero inspection result keeps the worktree and branch for manual review, with a warning naming both.
|
Thanks @liuhao1024 — this is a genuinely nasty P1 and your diagnosis was exactly right: the payload was pre-seeded with Merged via #88419, with your commit cherry-picked so your authorship is preserved in Three follow-up commits on top of yours, all from review — none of them a correction to your fix:
Also worth recording: your instinct to make the exception path fail-safe was already correct in the original code, and the sibling probes in Verification on the final stack: 21/21 in Thanks again — this was a well-written report with a deterministic offline reproduction, which made it fast to verify. Credit to @Adkid-Zephyr for the original issue (#88113) too. |
What does this PR do?
Stops
finalize_subagent_worktree()from permanently deleting a delegated child's uncommitted work when its Git inspection probes fail. Withdelegation.worktree_isolation: true, the function rangit rev-list --countandgit status --porcelainvia_run_git(check=False); on a non-zero exit it silently kept the payload defaults (commits: 0,dirty: False) and then read those defaults as proven clean state — runninggit worktree remove --forceandgit branch -D, so untracked/uncommitted files were irrecoverably lost and the result even reportedpruned: true(#88113, P1 data loss).The documented contract — only a worktree with zero commits and a clean tree is pruned, "anything holding work is kept" — is now enforced on the failure path too: a destructive cleanup requires affirmative proof. Any non-zero inspection result keeps the worktree and branch for manual review, with a WARNING naming both. The existing exception handler was already fail-safe; this closes the ordinary non-zero-exit gap beside it.
Related Issue
Fixes #88113
Type of Change
Changes Made
tools/subagent_worktree.py—finalize_subagent_worktree()tracks aninspection_okflag; a non-zerorev-listorstatusexit clears it, and a non-inspectable worktree returns un-pruned with a warning (worktree + branch named) instead of falling through to the prune condition.tests/tools/test_subagent_worktree.py— new regression reproducing the issue's deterministic offline scenario: a real git repo + worktree whose index is corrupted (makinggit status --porcelainexit 128) with uncommitted work on disk must stay intact — worktree present, WIP file present, branch not deleted,prunedfalse. The_githelper gained acheckparameter for the non-zero-exit sanity probe.How to Test
python -m pytest tests/tools/test_subagent_worktree.py -qtools/subagent_worktree.pyhunk stashed, the new test FAILS (worktree force-removed, WIP file gone, branch deleted — the data loss).python -m pytest tests/tools/test_delegate_subagent_timeout_diagnostic.py -q— Observed result: 2 passed, 2 failed; both failures are pre-existing on clean main (verified viagit stashA/B run) and unrelated to this change.worktree_exists: true,wip_exists: true,branch_exists: true,pruned: false.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs