BetterSWE DigestWhat is BetterSWE?

System Design · Issue 10

How GitHub Engineered Stacked Pull Requests for Millions of Repositories

Splitting a big change into a chain of small PRs used to mean rebasing branches by hand. Here is how GitHub made the stack a first-class object.

A huge single PR versus a tidy chain of small PRs

The problem

One giant PR, or a rebase treadmill

A huge single PR versus a tidy chain of small PRs

Every reviewer knows the bind. You either open one enormous pull request that takes forever to review, or you split the work across several dependent branches you then have to keep manually rebasing as things shift underneath you.

One big PR

slow, sprawling review

Stack of small PRs

reviewable, but manual to maintain

GitHub's own docs name three specific pains: rebasing dependent branches by hand is tedious and error-prone, reviewing a change out of context hurts review quality, and worst of all, branch protection rules and CI checks often fire only for the bottom PR in the chain.

That last one is the real technical gap. Without native support, only the PR targeting main gets full CI and branch-protection treatment, so every PR above it in the stack is a second-class citizen whose true status you cannot trust.

Anatomy

What a stack actually is

Three PR layers stacked on a main branch trunk

Stack — Two or more PRs in the same repo where the bottom one targets the stack's trunk (the repo default branch, e.g. main), and each PR above it targets the branch of the PR directly below.

main
 └─ PR #1  feature-a   → targets main   (bottom)
     └─ PR #2  feature-b → targets feature-a
         └─ PR #3  feature-c → targets feature-b (top)
Each PR's base is the branch of the one beneath it.

You drive it from a gh stack CLI extension, installed with gh extension install github/gh-stack. Commands like init, add, and submit track branches in dependency order and push them up as a linked stack on GitHub in one go.

The key decision

Evaluate every PR against the final target

Three stacked PRs all evaluated against the main branch

The clever move that fixes the second-class-PR problem is quiet but load-bearing. Every PR in a stack is checked for branch protection rules and required CI against its final target branch, not its literal base branch.

  literal base        evaluated against
  PR #3 → feature-b  ⇒   main   (CI + CODEOWNERS)
  PR #2 → feature-a  ⇒   main   (CI + CODEOWNERS)
  PR #1 → main       ⇒   main   (CI + CODEOWNERS)
Mid-stack PRs inherit the trunk's rules instead of their neighbour's.

So a CODEOWNERS approval rule or a CI workflow configured for main now triggers correctly on a PR three layers up, even though its direct base is feature-b. The status you see for the middle of the stack is finally the status you would get if that change landed on trunk.

The stack stops being a stack of guesses. Every layer is graded against where it is actually going.

Merging

Merge one, land everything below it

A stack merging bottom-up into main, with a merge queue lane beside it

Clicking Merge on any PR in the stack merges that PR and every unmerged PR below it, bottom-up, as a single operation. A direct merge is atomic: either the whole group lands, or on any failure nothing merges and it rolls back.

  1. Merge PR #2 (you click here)
  2. PR #1 lands first (it is below)
  3. PR #2 lands (onto the new trunk)
  4. PR #3 stays open (auto-rebased onto main)

Land only part of the stack and the PRs above stay open and automatically retarget onto the new base. All three merge methods work, and the resulting history is stated to match merging each PR individually from the bottom.

Through the merge queue the behaviour is subtler: PRs enter together but are evaluated one at a time from the bottom, and if one fails it and all its descendants are ejected while the lower PRs that already passed are untouched.

The hard part

Cascading rebase, and the squash trap

A server replaying a stack of branches onto an advanced trunk

Keeping a stack current is the genuinely hard problem, so GitHub moved it server-side. A Rebase Stack button (or gh stack rebase) replays the entire stack onto the latest trunk, updates every unmerged branch, and force-pushes the results in one cascade.

main advances ──▶ ▓▓▓ new trunk
        │  Rebase Stack (server-side)
        ▼
   replay #1 → #2 → #3, force-push each branch
One button rewrites the whole chain onto the fresh base.

The subtle win is squash-merge handling. Squash and rebase merges rewrite commit hashes, which historically broke the git-history continuity stacked PRs rely on to compute diffs. GitHub's rebase engine replays your unique commits on top of the squashed base, avoiding the artificial conflicts that used to make this the standard objection to stacking.

Rollout

Preview first, merge queue later

A rollout timeline from gated private preview to all repositories

The feature shipped in stages. A waitlist-gated private preview of the gh-stack CLI extension opened in April 2026, covered by InfoQ later that month.

Apr 2026 (private preview)Jul 2026 (public preview)weeks later (merge queue support)

Public preview arrived on July 30, 2026, rolling out to all repositories over the following days, while merge queue support for stacks rolled out progressively over the coming weeks, lagging the basic feature.

The launch drew a large Hacker News thread, though the reported numbers diverge across snapshots: HN itself showed around 900 points and 528 comments, while InfoQ cited 516 points and 282 comments for the discussion.

The tradeoff

Stacking, without the git bookkeeping

Why it matters

Stacked diffs were a Meta and Google workflow that tools like Graphite bolted onto GitHub from the outside. Bringing it inside the platform changes what mid-stack PRs mean: evaluating each one against its final target branch is the fix that makes CI and CODEOWNERS trustworthy for the whole chain, not just the bottom. For teams that already split work into dependent branches, native support removes the manual rebasing and the squash-merge breakage that made the pattern painful on GitHub.

The short version

  • A **stack** is dependent PRs where the bottom targets `main` and each one above targets the branch below it.
  • The key decision is evaluating every PR against its **final target branch**, so CI and branch protection fire on mid-stack PRs, not just the bottom one.
  • Merging any PR lands it and everything below atomically, and PRs above **auto-rebase** onto the new base.
  • A **server-side cascading rebase** replays the whole stack and safely handles squash merges, the old dealbreaker for stacking on GitHub.
  • Public preview landed July 30, 2026 across all repos, with **merge queue** support trailing by weeks.

Sources

  1. 4.Stacked pull requests are now in public preview
  2. 6.GitHub Stacked PRs | GitHub Stacked PRs
  3. 7.Overview | GitHub Stacked PRs
  4. 8.GitHub Stacked PRs
  5. 12.GitHub Targets Large Merge Problem with Stacked PRs - InfoQ