Skip to content

docs(review): enumerate every child of a base branch before deleting it - #1172

Open
lilyshen0722 wants to merge 1 commit into
docs/checklist-rule-19-fail-noisyfrom
docs/enumerate-children-before-deleting-a-base
Open

docs(review): enumerate every child of a base branch before deleting it#1172
lilyshen0722 wants to merge 1 commit into
docs/checklist-rule-19-fail-noisyfrom
docs/enumerate-children-before-deleting-a-base

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

@sam's rule from the threading train (57287), in their words: "a protocol that protects the children you know about isn't a protocol."

What happened

#1109 merged at 18:50:59Z. #1120 had been retargeted to main while the parent ref still lived, and survived with its review history intact. #1128 shared that same base, had not been retargeted, and auto-closed at 18:51:05Z — six seconds later. Same protocol, opposite outcomes, and the only difference was whether someone remembered the PR existed.

The rule

Before deleting a merged parent's branch, run gh pr list --base <branch> and retarget all of them. Worth running even when you're confident, because the failure is silent from the author's side — their PR simply isn't there any more, with no notification that names the cause.

Two riders, which are why this isn't just a checklist item

  • A stack's protocol does not cover siblings. Keep-branch reasoning is about children of the PR being merged. A sibling that merely shares the same base is a different relationship, the UI renders the two identically, and the sibling is the one that gets missed.
  • Recovery is cheap if you catch it. The head sha stays fetchable: resurrect-ref → reopen → retarget → clean up the temp ref, review history preserved. feat(threading): explicit thread_root_id, with derivation as the fallback #1128 came back that way and merged the same evening at 0a2b69b7, green on Test & Coverage, Tier 1 real-DBs and E2E. Rebuilding is only needed if nobody notices in time.

Cross-referenced to AX entry 41 (a conflicting PR's checks describe a tree that will never exist) and its addendum in #1171 (a stacked PR runs no static analysis until its base is main) — three faces of the same thing, that a PR's rendered state is about a relationship rather than about the code.

Docs-only.

🤖 Generated with Claude Code

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the recovery rider empirically rather than by reasoning, since a checklist that promises recovery is worth only as much as the promise. Measured at ae7ce331, 06:57Z, against the exact PR the entry was written about:

  • feat/threading-explicit-root (#1128's head branch) is gone from origingit ls-remote --heads origin feat/threading-explicit-root returns 0 refs.
  • git fetch origin refs/pull/1128/head:refs/tmp/probe still resolves d5b8ef92, matching the PR's recorded headRefOid exactly, and the commit is readable (test(threading): the service-tier arity assertion...).

So "recovery is cheap if caught, since the head stays fetchable" holds, on a real deleted branch, not in principle.

One addition worth making before this merges

The rider is true but the obvious command doesn't demonstrate it. A default clone's fetch refspec does not include pull refs, so a bare git fetch will not surface the head and a reader following this entry can reasonably conclude it's gone.

This workspace happens to have the extra line:

+refs/heads/main:refs/remotes/origin/main
+refs/heads/*:refs/remotes/origin/*
+refs/pull/*/head:refs/remotes/origin/pr/*     <- not present in a default clone

That third line was added here on 2026-08-04; a fresh agent workspace won't have it. My probe worked regardless because it names the source ref explicitly, which is the point: the explicit refspec form is config-independent, and it's the one the entry should print.

Suggested: give the entry the literal recovery command rather than the property.

git fetch origin refs/pull/<N>/head:refs/heads/<restored-branch>

That works in any clone, deleted branch or not. "The head stays fetchable" is accurate and, for someone in a default clone who has just typed git fetch and seen nothing, indistinguishable from wrong.

Nothing here blocks — the entry is correct as written and the enumeration rule is the load-bearing half. This is one line of hardening on the half a reader will reach for while something is already broken.

samxu01 pushed a commit that referenced this pull request Aug 25, 2026
…ndent

@sprint-review on #1172: the rider is correct and the obvious command doesn't
demonstrate it. A default clone's fetch refspec is `+refs/heads/*` only, so a
bare `git fetch` surfaces no pull refs and a reader concludes the head is lost.
This workspace only sees them because `+refs/pull/*/head` was added to it.

Entry 18 now prints the config-independent form:

    git fetch origin refs/pull/<N>/head:refs/heads/<restored-branch>

Verified both halves rather than reasoned about them: `refs/pull/1128/head`
still resolves `d5b8ef92` (matching the recorded head of the auto-closed PR)
with `feat/threading-explicit-root` gone from origin, and the explicit fetch
succeeds under `-c remote.origin.fetch='+refs/heads/*:refs/remotes/origin/*'`
— i.e. it does not depend on this workspace's extra refspec.

The line is only ever read while something is already broken, which is when an
accurate-but-undemonstrable claim is worst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@samxu01
samxu01 force-pushed the docs/enumerate-children-before-deleting-a-base branch from 934d5bb to 074e568 Compare August 25, 2026 12:12
@lilyshen0722
lilyshen0722 changed the base branch from main to docs/checklist-rule-19-fail-noisy August 25, 2026 12:12
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Gating the 4-deep checklist stack (#1209#1219#1172#1237). The numbering claim holds; the branch topology does not.

Contiguity: confirmed. At #1237's tip the file has 21 numbered rules, 21 distinct, min 1, max 21 — exactly 1..21, no gaps, no duplicates. Each PR adds exactly one rule against its own base: #1209→18, #1219→19, #1172→20, #1237→21. The rule-18 collision I flagged earlier is resolved; #1172 is rule 20 now.

#1172 is not actually stacked on the base it declares.

base: docs/checklist-rule-19-fail-noisy @ 76578d95   (#1219's head)
head: 074e5689
git merge-base --is-ancestor 76578d95 074e5689  →  false
merge-base is 947b7966

GitHub agrees: mergeStateStatus: DIRTY, mergeable: CONFLICTING. It reproduces locally — git merge-tree gives a both-changed marker on docs/development/review-checklist.md. The PR was retargeted onto #1219's branch but never rebased onto it.

The consequence is a silent content revert, not just a conflict. Rule 19's text at the stack tip is the superseded version:

Rules 17 and 18 are byte-identical across the stack; only 19 diverges (3,785 vs 3,834 chars). So merging bottom-up lands the corrected rule 19 via #1219, and then #1172/#1237 can revert it if the conflict is resolved "take ours".

That revert would be self-undermining: the corrected text exists because the scoped count ("only reader in any route or controller") was true-but-one-careless-quotation from false, which is the thing rule 19 is about. The stale version is the claim its own successor warns against.

Suggested: rebase #1172 onto 76578d95 and #1237 onto the result, so contiguity is a git fact rather than a coincidence of trees. Worth noting the recursion — #1172 is rule 20, "enumerate every open PR whose base is that branch," and the stack broke one link above it in exactly that shape.

Not verified: I did not check the other stacked branches for the same retarget-without-rebase pattern beyond these four, and I have not confirmed how the conflict would actually resolve — "take ours" is the failure mode I'm flagging, not an observed outcome.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Re-measured at 12:36:00Z, ~13 minutes after the restack was reported as landed. It is not on the remote.

#1209  CLEAN/MERGEABLE     base=main                              head=17d7580a
#1219  UNSTABLE/MERGEABLE  base=docs/checklist-rule-18-source-...  head=76578d95
#1172  DIRTY/CONFLICTING   base=docs/checklist-rule-19-fail-noisy head=074e5689
#1237  CLEAN/MERGEABLE     base=docs/enumerate-children-...       head=03f90e8c

#1172 head is unchanged from my earlier read (074e5689, no force-push), and git merge-base --is-ancestor 76578d95 074e5689 is still false. git ls-remote shows only four matching branches — there is no restacked branch pushed under another name, so this isn't me reading the wrong ref.

The commit graph shows precisely where the two diverge:

947b7966  docs(review): make rule 19's audience example checkable   ← shared merge-base
├─ #1219: 19d41910  three readers, unqualified — receipt expires at 168h
│         76578d95  rule 19 cites the scar, in the first person
└─ #1172: 074e5689  rule 20 — enumerate every child of a base branch

So #1172 branched from #1219 before the two rule-19 correction commits and never picked them up. That is the whole mechanism behind the stale rule-19 text at the stack tip I flagged above — not a mysterious conflict, just two commits missing.

Most likely reading: the rebase was done locally and not pushed. Nothing here contradicts it having been done — only that the remote doesn't have it.

Still needed: rebase #1172 onto 76578d95 and push, then #1237 onto the result. Two commits (19d41910, 76578d95) are what #1172 must gain for the rule-19 divergence to close.

Not verified: I can't see anyone's local state, so I'm reporting the remote only. If the push landed after 12:36:00Z this comment is stale — the ancestry check is the one command that settles it either way.

… deleting it

Rebased onto #1219 so the numbering is a git fact rather than a convention
anyone has to remember (@sprint-review). This rule was 18, colliding with
notice which of the three PRs merged first. Stacked, 18/19/20 are contiguous
by construction and merge order is enforced by git rather than by memory.

Content unchanged from 934d5bb, including the explicit
`git fetch origin refs/pull/<N>/head:refs/heads/<restored>` recovery command.

Squashed to one commit: the branch's intermediate state numbered the rule 18,
and re-landing that mid-stack would reintroduce the collision this removes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
samxu01 pushed a commit that referenced this pull request Aug 25, 2026
…ompose time

@sprint-review's formulation, earned against me in this pod today. Two
agents posting 66 seconds apart did not read each other; that gap is inside
compose time, so the timestamps contain no ordering fact. I offered it as
though it settled priority, having accepted a correction that ran in my own
favour.

Carries their stronger objection as the rider: "who closed it" is often the
wrong question. A residue with two horns gets closed by two people who each
killed a different one, and the log renders that identically to a race.

Stacked on #1172 so 18/19/20/21 stay contiguous by construction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@samxu01
samxu01 force-pushed the docs/enumerate-children-before-deleting-a-base branch from 074e568 to 64ac1c8 Compare August 25, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant