We've run several stacked-PR efforts (the telemetry restructure, local-rule-routing) and hit avoidable footguns each time. Capture the workflow and the hard-won learnings in CLAUDE.md so both agents and humans handle stacks consistently.
Ask
Add a "Stacked PRs with Git Town" section to CLAUDE.md covering the items below.
Learnings to capture
Landing a stack: merge down, then one merge to main
Branch protection lives on main only (Validate required, strict_up_to_date: true, 0 required reviews). Merging each PR down into its parent branch (tip → bottom) is unprotected and instant, so:
- Merge
#tip into its parent's branch, then that into the next parent, … down to the bottom branch (which targets main). The bottom branch accumulates the whole stack.
- Bring the bottom branch up to date with
main, let Validate pass, then do the single protected merge to main.
- Result: one CI cycle instead of N, and every PR gets a real Merged badge (not "closed/absorbed").
Never --delete-branch mid-stack
gh pr merge <n> --delete-branch on a stacked PR closes the child PR (its base branch vanished) instead of retargeting it. Leave branches in place during the stack; clean them up only after the whole stack has landed.
Use merge-commit, not squash, for a stack
Stacked branches share commits (each child contains its ancestors). --merge keeps children clean; squash rewrites the parent into a new commit the children don't have, forcing a git town sync reconciliation between every merge and inviting phantom conflicts. (Note: git town ship's default strategy can be squash — prefer explicit gh pr merge --merge.)
Recovery if a child PR gets closed by base-branch deletion
- Restore the deleted base branch ref at the merge commit's second parent:
gh api --method POST repos/<owner>/<repo>/git/refs -f ref=refs/heads/<branch> -f sha=$(git rev-parse origin/main^2)
- Reopen the child via REST (GraphQL
gh pr reopen fails on the Projects-classic deprecation): gh api --method PATCH repos/<owner>/<repo>/pulls/<n> -f state=open
- Retarget it:
gh pr edit <n> --base main (only works once it's open).
Other gotchas
- Projects-classic deprecation breaks several GraphQL-backed
gh commands (gh pr reopen, git-town's gh connector updating proposals). Workarounds: REST API for PR state changes; git-town api connector (GITHUB_TOKEN) for proposals.
gh pr update-branch may not exist in the installed gh; update locally (git merge origin/main on the up-to-date remote branch) and push.
- Stack-aware OpenSpec archive check (
pr-check-openspec.yml) skips on non-tip PRs and runs on the tip; "tip" recomputes as branches merge, so it lands green when the archiving PR reaches main.
git town sync --all propagates fixes up the stack and prunes; run it after the stack lands to clean local branches.
Source
These come from the telemetry stack landing on 2026-06-14 (PRs #32–#37) and the local-rule-routing stack review.
We've run several stacked-PR efforts (the telemetry restructure, local-rule-routing) and hit avoidable footguns each time. Capture the workflow and the hard-won learnings in
CLAUDE.mdso both agents and humans handle stacks consistently.Ask
Add a "Stacked PRs with Git Town" section to
CLAUDE.mdcovering the items below.Learnings to capture
Landing a stack: merge down, then one merge to main
Branch protection lives on
mainonly (Validaterequired,strict_up_to_date: true, 0 required reviews). Merging each PR down into its parent branch (tip → bottom) is unprotected and instant, so:#tipinto its parent's branch, then that into the next parent, … down to the bottom branch (which targetsmain). The bottom branch accumulates the whole stack.main, letValidatepass, then do the single protected merge tomain.Never
--delete-branchmid-stackgh pr merge <n> --delete-branchon a stacked PR closes the child PR (its base branch vanished) instead of retargeting it. Leave branches in place during the stack; clean them up only after the whole stack has landed.Use merge-commit, not squash, for a stack
Stacked branches share commits (each child contains its ancestors).
--mergekeeps children clean; squash rewrites the parent into a new commit the children don't have, forcing agit town syncreconciliation between every merge and inviting phantom conflicts. (Note:git town ship's default strategy can be squash — prefer explicitgh pr merge --merge.)Recovery if a child PR gets closed by base-branch deletion
gh api --method POST repos/<owner>/<repo>/git/refs -f ref=refs/heads/<branch> -f sha=$(git rev-parse origin/main^2)gh pr reopenfails on the Projects-classic deprecation):gh api --method PATCH repos/<owner>/<repo>/pulls/<n> -f state=opengh pr edit <n> --base main(only works once it's open).Other gotchas
ghcommands (gh pr reopen, git-town'sghconnector updating proposals). Workarounds: REST API for PR state changes; git-townapiconnector (GITHUB_TOKEN) for proposals.gh pr update-branchmay not exist in the installed gh; update locally (git merge origin/mainon the up-to-date remote branch) and push.pr-check-openspec.yml) skips on non-tip PRs and runs on the tip; "tip" recomputes as branches merge, so it lands green when the archiving PR reachesmain.git town sync --allpropagates fixes up the stack and prunes; run it after the stack lands to clean local branches.Source
These come from the telemetry stack landing on 2026-06-14 (PRs #32–#37) and the local-rule-routing stack review.