Skip to main content

Stack Upkeep — Stacked PRs Across Squash Merges

How to keep a chain of stacked PRs healthy as parents merge into main. Battle-tested across four parallel tracks (~28 stacked PRs) in the June 2026 Shopify-app uplift. Audience: implementer teammates doing stack upkeep, and leads dispatching it.

The core problem

This repo squash-merges. After a parent PR squash-merges, every child branch still carries the parent's ORIGINAL commits, which no longer exist on main in that form. GitHub then reports "merge conflicts" on the child — but they are almost never real content conflicts; they're the child's stale copy of the parent colliding with its own squashed equivalent.

The recipe: boundary rebase

Replay ONLY your own commits onto the new base; let the parent's stale copies fall away:

git fetch origin main
# <old-parent-tip> = the commit your branch was built on (the parent
# branch's tip BEFORE its squash-merge)
git rebase --onto origin/main <old-parent-tip> <your-branch>
  • Expect zero manual conflict hunks. If git stops on a commit that is your parent's content, it is patch-already-upstreamgit rebase --skip is correct (the squash brought that content in already).
  • If you hit a conflict in YOUR OWN commit's hunks, STOP and tell the lead — that's a real conflict and may warrant a delta re-verify of the resolution.
  • Re-run the full gates after every rebase. The merged parent's tests on main are now the live ratchet — they must pass UNMODIFIED.
  • Push with --force-with-lease, your feature branch only. Never force-push anything else (CLAUDE.md universal gate).
  • Then retarget the PR's base to main (or to the next unmerged parent).

Hazard: the clean cherry-pick that reverts review fixes

Never rebuild a stack by cherry-picking onto a remembered base. A cherry-pick can apply "cleanly" against a PRE-review version of a file and silently revert fixes that review rounds added to the parent. (Caught live in the uplift: a re-cherry-picked test applied cleanly and would have undone a reviewer's raw-item assertion fix; only a merge-based rebuild surfaced it.) When a child must absorb an updated parent, prefer merging the parent branch in (or the boundary rebase above) over cherry-picks, and afterwards verify the parent's review-round changes are present in the final tree.

Hazard: shared-hunk files copied across stacks

When the same new file must exist on two independent stacks (e.g. a test setup file needed by both a main-based test PR and a refactor stack), the copies must stay byte-identical or they add/add-conflict when the second stack merges. Discipline:

  • Copy via git checkout <source-branch> -- <path>, never by hand.
  • After either side changes in review, re-sync the other side immediately.
  • Verify with blob hashes (git ls-tree <branch> <path>) — identical hash = guaranteed clean merge.
  • Full-stack dry run: git merge-tree --write-tree origin/main <stack-tip> exits 0 with no conflict section iff the whole stack merges cleanly.

Choreography when a parent merges

  1. Lead (or merge watcher) announces the merge.
  2. Child owner: retarget child PR base → main, boundary-rebase, gates, --force-with-lease, confirm PR is MERGEABLE.
  3. Walk the remaining chain in order — each grandchild rebases onto the freshly rebased child.
  4. Force-pushes dismiss bot approvals; CI and the auto-reviewer re-run. This is expected — flag it in the PR if a human already approved.
  5. With automerge enabled, children land as approvals arrive — the lead's merge watcher (see team-lead-protocol) catches each landing and triggers the next walk.