A merged pull request is necessary before removing a worktree. It is not
sufficient. The worktree for PR #1035 was reaped while its branch carried two
commits that were on neither main nor its own remote branch, and only a rescue
ref preserved the SHAs.
The rule
Before git worktree remove, both of these must hold:
git log --oneline @{u}..HEAD is EMPTY, so nothing local is unpushed.
- The content reached
main, checked by content rather than by ancestry.
Either one alone gives the wrong answer. Section "The four instruments
disagreed" below measures that rather than asserts it.
Why ancestry cannot answer this
main is squash-only. A squash writes a NEW commit whose tree matches the
branch tip and whose parent is main, so no commit from the branch is ever an
ancestor of main, and no patch-id from the branch survives either.
git merge-base --is-ancestor therefore returns false for work that landed
perfectly, and git cherry origin/main marks landed commits +. Neither
instrument distinguishes "landed via squash" from "never pushed".
The four instruments disagreed, measured on the #1035 case
PR #1035 (row/ENG-EXPERT-STREAM-CUDA-GROUPED-LINEAR) merged 2026-08-16T21:09:11Z
as squash b493f4981 on main. The rescue ref
rescue/es-cuda-grouped-unpushed holds the reaped worktree's branch at
3ca7c23d8, three commits above ff264cb82:
3ca7c23d8 record(ENG-EXPERT-STREAM): re-derive the codebook-coverage figure instead of quoting it
c2acaac91 fix(ENG-EXPERT-STREAM): bind the captured case name so it cannot dangle at failure time
5bc299f08 fix(ENG-EXPERT-STREAM): the grouped CUDA dispatch launched nothing and called it success
| Instrument |
Answer |
Correct? |
git merge-base --is-ancestor <c> origin/main |
all three NOT on main |
no, squash |
git cherry -v origin/main 3ca7c23d8 |
all three +, meaning missing upstream |
no, squash changes the patch-id |
git cherry -v <remote branch> 3ca7c23d8 |
5bc299f08 +, the other two - |
misleading, 5bc299f08 is the same change on a different base |
git log --oneline <remote branch>..3ca7c23d8 |
all three unpushed by SHA |
yes, and it is the guard that would have fired |
The remote branch row/ENG-EXPERT-STREAM-CUDA-GROUPED-LINEAR sits at 9e34cc841
and carries the SAME three subjects at DIFFERENT SHAs (c3540e4e2, 604677db1,
9e34cc841). So the local commits were never the pushed commits, and step 1
above would have refused the reap.
What was actually at risk, stated honestly
Step 2 passes on this case. The content of all three commits IS on origin/main
at b493f4981, verified two ways:
git diff c2acaac91 origin/main -- tests/vt/test_cuda_quant_dot.cpp is empty.
git log -S're-derived here rather than quoted' origin/main -- .agents/specs/expert-streaming.md names b493f4981.
So the reap lost nothing, and the rescue ref preserved SHAs that were already
redundant. That is the strongest argument for the two-part rule rather than a
weaker one. Step 1 alone would have blocked a safe reap. Step 2 alone would have
allowed it. Only running both says which of the two situations you are in, and
the reaper had no way to tell them apart at the time.
The rescue ref
rescue/es-cuda-grouped-unpushed, tip 3ca7c23d8. Named here so the owner can
recover the work if the content check is ever found wrong. Delete it when the
owner confirms it is redundant. Two sibling refs exist from earlier incidents,
rescue/cuda-breadth-sm75-audit (fe4d91aa6) and rescue/fp8-native
(1d919db19), and neither has been adjudicated.
Scope
This asks for the rule to be recorded where a reaper reads it, and for the
two-part check to be executable rather than remembered. It does not ask for a
gate that runs on every pull request: reaping is not a pull-request event, and a
checker that must query the remote cannot run here.
Related: #788 records the opposite direction, where an UNdeleted branch reads
IN-FLIGHT forever on a squash-only main. Both are the same root cause: on a
squash-only main, ancestry carries no information about a branch.
A merged pull request is necessary before removing a worktree. It is not
sufficient. The worktree for PR #1035 was reaped while its branch carried two
commits that were on neither
mainnor its own remote branch, and only a rescueref preserved the SHAs.
The rule
Before
git worktree remove, both of these must hold:git log --oneline @{u}..HEADis EMPTY, so nothing local is unpushed.main, checked by content rather than by ancestry.Either one alone gives the wrong answer. Section "The four instruments
disagreed" below measures that rather than asserts it.
Why ancestry cannot answer this
mainis squash-only. A squash writes a NEW commit whose tree matches thebranch tip and whose parent is
main, so no commit from the branch is ever anancestor of
main, and no patch-id from the branch survives either.git merge-base --is-ancestortherefore returns false for work that landedperfectly, and
git cherry origin/mainmarks landed commits+. Neitherinstrument distinguishes "landed via squash" from "never pushed".
The four instruments disagreed, measured on the #1035 case
PR #1035 (
row/ENG-EXPERT-STREAM-CUDA-GROUPED-LINEAR) merged 2026-08-16T21:09:11Zas squash
b493f4981onmain. The rescue refrescue/es-cuda-grouped-unpushedholds the reaped worktree's branch at3ca7c23d8, three commits aboveff264cb82:git merge-base --is-ancestor <c> origin/maingit cherry -v origin/main 3ca7c23d8+, meaning missing upstreamgit cherry -v <remote branch> 3ca7c23d85bc299f08+, the other two-5bc299f08is the same change on a different basegit log --oneline <remote branch>..3ca7c23d8The remote branch
row/ENG-EXPERT-STREAM-CUDA-GROUPED-LINEARsits at9e34cc841and carries the SAME three subjects at DIFFERENT SHAs (
c3540e4e2,604677db1,9e34cc841). So the local commits were never the pushed commits, and step 1above would have refused the reap.
What was actually at risk, stated honestly
Step 2 passes on this case. The content of all three commits IS on
origin/mainat
b493f4981, verified two ways:git diff c2acaac91 origin/main -- tests/vt/test_cuda_quant_dot.cppis empty.git log -S're-derived here rather than quoted' origin/main -- .agents/specs/expert-streaming.mdnamesb493f4981.So the reap lost nothing, and the rescue ref preserved SHAs that were already
redundant. That is the strongest argument for the two-part rule rather than a
weaker one. Step 1 alone would have blocked a safe reap. Step 2 alone would have
allowed it. Only running both says which of the two situations you are in, and
the reaper had no way to tell them apart at the time.
The rescue ref
rescue/es-cuda-grouped-unpushed, tip3ca7c23d8. Named here so the owner canrecover the work if the content check is ever found wrong. Delete it when the
owner confirms it is redundant. Two sibling refs exist from earlier incidents,
rescue/cuda-breadth-sm75-audit(fe4d91aa6) andrescue/fp8-native(
1d919db19), and neither has been adjudicated.Scope
This asks for the rule to be recorded where a reaper reads it, and for the
two-part check to be executable rather than remembered. It does not ask for a
gate that runs on every pull request: reaping is not a pull-request event, and a
checker that must query the remote cannot run here.
Related: #788 records the opposite direction, where an UNdeleted branch reads
IN-FLIGHTforever on a squash-onlymain. Both are the same root cause: on asquash-only
main, ancestry carries no information about a branch.