git-worktree-reap-deletes-fresh-uncommitted-worktree
The solution is written to ~/git-worktree-reap-solution.md. Summary of what I did and verified:
The machine had no ~/.hermes/scripts/worktree.sh, so I confirmed the project conventions against the real <project> repo (cloned from GitHub) and reproduced the core git semantics locally.
Root cause: reap used branch ancestry as a proxy for "task done". A zero-commit branch (branch_sha == base_sha) is trivially reported as merged:
$ git branch --merged main
* main
task/zero-commit # zero own commits, yet "merged"
$ git merge-base --is-ancestor task/zero-commit main # exit 0
$ git rev-list --count main..task/zero-commit
0
--all then applied that false signal to a worktree created seconds earlier, so git worktree remove + git branch -D deleted it under the worker — matching the empty -Q log, missing dir, and empty git worktree list.
Two independent gates plus a same-tick guard in the reap path:
1. Age gate — skip any worktree younger than WORKTREE_REAP_MIN_AGE_SECS (default 600s), using a create-time marker, falling back to the worktree admin gitdir ctime.
2. Task-closed evidence — reap only when the task is closed per .coding-hermes/board/tasks.jsonl (status: complete|closed|done|...) or a .coding-hermes/tasks/<id>/closed marker. Branch-merging is never consulted.
3. Same-tick guard — a created-<tick> manifest blocks reaping anything created this tick.
Plus scheduler policy: don't invoke reap --all in a creation tick.
A 7-assertion harness reproducing the exact incident shape (zero-commit worktrees on the base tip) passes:
Scenario 1: fresh zero-commit worktrees -> both survive age gate
Scenario 2: aged; CLOSED reaped, OPEN survives (no merged inference)
Scenario 3: same-tick created worktree survives even if aged+closed
Scenario 4: detached worktree survives
RESULT: 7 passed, 0 failed
The embedded script was separately extracted from the markdown and bash -n-checked; it is functionally identical to the tested reference.
# Evidence - Problem class: git-worktree-reap-deletes-fresh-uncommitted-worktree - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-20T11:38:31.852Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "worktree.sh reap --all deleted a JUST-CREATED zero-commit worktree: a newly created branch (branch==base_sha, no own commits) classifies as MERGED, so reap removes the worktree and deletes the branch even though a worker was dispatched into it seconds ago. Symptom: worker pid dead in <60s with EMPTY -Q log, worktree dir gone, git worktree list empty. Fix: never run reap --all in the same tick/session that created worktrees; reap only worktrees whose task is closed, or age-gate the reap against worktree creation time.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "git-worktree-reap-deletes-fresh-uncommitted-worktree", "provider": "openrouter", "solved_at": "2026-09-20T11:38:31.853Z", "version": ""}The solution is written to ~/git-worktree-reap-solution.md. Summary of what I did and verified:
The machine had no ~/.hermes/scripts/worktree.sh, so I confirmed the project conventions against the real <project> repo (cloned from GitHub) and reproduced the core git semantics locally.
Root cause: reap used branch ancestry as a proxy for "task done". A zero-commit branch (branch_sha == base_sha) is trivially reported as merged:
$ git branch --merged main
* main
task/zero-commit # zero own commits, yet "merged"
$ git merge-base --is-ancestor task/zero-commit main # exit 0
$ git rev-list --count main..task/zero-commit
0
--all then applied that false signal to a worktree created seconds earlier, so git worktree remove + git branch -D deleted it under the worker — matching the empty -Q log, missing dir, and empty git worktree list.
Two independent gates plus a same-tick guard in the reap path:
1. Age gate — skip any worktree younger than WORKTREE_REAP_MIN_AGE_SECS (default 600s), using a create-time marker, falling back to the worktree admin gitdir ctime.
2. Task-closed evidence — reap only when the task is closed per .coding-hermes/board/tasks.jsonl (status: complete|closed|done|...) or a .coding-hermes/tasks/<id>/closed marker. Branch-merging is never consulted.
3. Same-tick guard — a created-<tick> manifest blocks reaping anything created this tick.
Plus scheduler policy: don't invoke reap --all in a creation tick.
A 7-assertion harness reproducing the exact incident shape (zero-commit worktrees on the base tip) passes:
Scenario 1: fresh zero-commit worktrees -> both survive age gate
Scenario 2: aged; CLOSED reaped, OPEN survives (no merged inference)
Scenario 3: same-tick created worktree survives even if aged+closed
Scenario 4: detached worktree survives
RESULT: 7 passed, 0 failed
The embedded script was separately extracted from the markdown and bash -n-checked; it is functionally identical to the tested reference.
# Evidence - Problem class: git-worktree-reap-deletes-fresh-uncommitted-worktree - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-20T11:38:31.852Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "worktree.sh reap --all deleted a JUST-CREATED zero-commit worktree: a newly created branch (branch==base_sha, no own commits) classifies as MERGED, so reap removes the worktree and deletes the branch even though a worker was dispatched into it seconds ago. Symptom: worker pid dead in <60s with EMPTY -Q log, worktree dir gone, git worktree list empty. Fix: never run reap --all in the same tick/session that created worktrees; reap only worktrees whose task is closed, or age-gate the reap against worktree creation time.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "git-worktree-reap-deletes-fresh-uncommitted-worktree", "provider": "openrouter", "solved_at": "2026-09-20T11:38:31.853Z", "version": ""}