Prerequisite: You should be comfortable with Git
Conventions and with creating
worktrees, since every PR in a stack gets its own.
TL;DR
A stack is a chain of pull requests where each one targets the branch below it instead ofmain. Use it when a change is too large to review in one sitting,
or when follow-up work builds on a branch that has not merged yet.
GitHub has a first-class feature for this, currently in public preview. It
tracks the chain as a unit, and merging one pull request rebases and retargets
the rest. Use it when you can:
Native stacks vs. base chaining
Two things get called “stacked PRs”, and they behave differently on merge. Native stacks — created withgh stack or the web UI. GitHub knows the
chain exists. Merging a mid-stack pull request merges everything below it, and
“the pull requests above stay open and automatically re-target the stack’s base
branch.” All three merge methods work: “Stacks support merge commit, squash,
and rebase merge methods, and they are merge-queue aware.”
Base chaining — plain gh pr create --base <parent-branch>, with no stack
object behind it. This is what you get by default, and what the rest of this
page’s manual steps describe. It mostly works, but the merge-time guarantees
above do not apply to it; see Base chaining without a native
stack.
Limits on the native feature, both from GitHub: it “is in public preview and
subject to change”, and it “require[s] all branches to be in the same
repository. Cross-fork stacks are not supported.” It is also “not supported in
GitHub Desktop”, so a contributor working from a fork is on the base-chaining
path whether they want to be or not.
When to stack
Stack when the parts are genuinely dependent:- A large feature that splits into reviewable slices, where slice 2 imports code slice 1 introduces.
- Follow-up work on a branch that is still waiting for review — stacking lets you keep building instead of idling or piling more into a PR someone has already started reviewing.
- A refactor that must land before the change that depends on it, where you want them reviewed separately but merged together.
main: they can merge in any order, neither blocks the other, and
neither needs a rebase when the other lands. A stack buys ordering at the cost
of rebasing, so only pay it when you need the ordering.
Creating a stack
Each PR gets its own worktree, per the repo-wide rule that PR work never switches the shared checkout:main; every
later one targets its parent:
Say where each PR sits
Reviewers cannot see the stack from the PR page alone. Put the position and the parent in the body of every PR above the base:Keeping each PR green
Every PR in the stack must pass on its own. A child that only compiles once its parent is merged is not reviewable, and CI will say so. Run the usual gates in the child’s worktree, not just at the top of the stack:bun check does not compile Next apps, so a slice that touches routes, pages
or dependencies also needs the affected app’s real build before it can be
called verified.
Merging the stack
Merge bottom-up, always. GitHub states it as a requirement, not a preference: “pull requests must merge from the bottom up.” Never merge a pull request whose parent is still open and unmerged — that pulls the parent’s unreviewed commits intomain through the child.
How much lands depends on which one you merge:
On a native stack that is the whole procedure. The merge method is free —
“Stacks support merge commit, squash, and rebase merge methods, and they are
merge-queue aware” — and the branches above are rebased and retargeted for you.
Base chaining without a native stack
A chain built withgh pr create --base has no stack object behind it, so none
of the guarantees above apply. Two extra rules:
Merge the parent with a merge commit.
--squash and not --rebase. Both rewrite the parent’s commits into new
ones, so the originals never enter main’s ancestry — and the child, once
retargeted, shows the parent’s changes all over again as if they had never
landed. A merge commit keeps them reachable from main, which is what makes
the child’s diff shrink to its own work. If a parent has already been
squash-merged, rebase the child onto main, drop the duplicated commits, and
re-run its gates.
Check the retargeting actually happened. When a merged parent’s branch is
deleted through the pull-request flow — the merge button, or the repository’s
automatic head-branch deletion — GitHub retargets any open PR that had it as a
base onto the parent’s base.
Either way
Each pull request needs its own approving review from a code owner and its own resolved review threads — the repository ruleset applies per PR, not per stack. Follow the normal merge closeout (quiet window, main-green verification,bun git-sync, production verification) for each one that lands.
Rebasing a stack
If you rebase or amend a parent branch, every branch above it still points at the old commits and must be rebased too, from the bottom up:--force-with-lease so a push is refused if
someone else moved the branch:
Cleaning up
Remove a worktree and delete its local branch only after that PR is confirmed merged intomain. Never remove a worktree that is dirty, blocked, unmerged, or
owned by someone else.