We've all been there. You're three days into a feature, your branch has 47 changed files, and you know — you know — that whoever reviews this PR is going to skim it at best. Maybe they'll leave a "looks good" after glancing at the first two files. Maybe they'll rightfully push back and ask you to break it up. Either way, nobody's happy.
Large PRs are where bugs hide. They're where subtle architectural mistakes slip through. And they're where your teammates' goodwill goes to die.
The solution isn't new, but the tooling has finally caught up: stacked pull requests.
The Root Cause: Git Doesn't Know About PR Chains
The fundamental problem is that git branches are independent. You can base one branch on another, sure, but git gives you zero help managing the relationship between them. When you rebase the first branch, every downstream branch needs to be rebased too — manually.
Here's what a typical attempt at breaking up work looks like without proper tooling:
# Start with a data model change
git checkout -b feature/add-user-roles
# ... make changes, push, open PR #1
# Now build the API layer on top
git checkout -b feature/roles-api
# ... make changes, push, open PR #2 (based on PR #1)
# Now the UI
git checkout -b feature/roles-ui
# ... make changes, push, open PR #3 (based on PR #2)
This looks fine until a reviewer asks you to change something in PR #1. Now you need to:
- Make the fix on
feature/add-user-roles - Rebase
feature/roles-apionto the updated branch - Resolve any conflicts
- Rebase
feature/roles-uionto the updatedfeature/roles-api - Resolve more conflicts
- Force-push all three branches
Do this three or four times during review, and you'll swear off small PRs forever. That's the trap — the tooling pain pushes developers toward monolithic PRs, which are worse for everyone.
Enter Stacked PRs: The Workflow
The stacked PR workflow treats a chain of dependent branches as a single unit. You still get small, focused, reviewable PRs, but the rebasing and syncing headaches are handled for you.
GitHub has released gh-stack as a GitHub CLI extension that brings first-party support for this workflow. Here's how to get started.
Step 1: Install the Extension
Since gh-stack is a gh CLI extension, you'll need the GitHub CLI installed first:
# Install the gh-stack extension
gh extension install github/gh-stack
Step 2: Build Your Stack From the Bottom Up
The mental model is simple: each branch in your stack builds on the one below it. Start with the lowest-level change and work your way up.
# Create your first branch and make changes
git checkout -b refactor/extract-auth-service main
# ... write code ...
git add -A && git commit -m "Extract auth logic into AuthService class"
# Create the next branch ON TOP of the first
git checkout -b feature/oauth-providers refactor/extract-auth-service
# ... write code ...
git add -A && git commit -m "Add OAuth provider support to AuthService"
# And another layer
git checkout -b feature/oauth-ui feature/oauth-providers
# ... write code ...
git add -A && git commit -m "Add OAuth provider selection UI"
Each branch contains only the diff relevant to that layer. Reviewers see small, focused changes. You maintain logical separation.
Step 3: Create the Stack of PRs
With gh-stack, you can create and manage the entire chain:
# Push and create PRs for the whole stack
gh stack create
This creates PRs with the correct base branches — PR #2 targets the branch of PR #1 instead of main, so each PR's diff only shows its own changes. No more scrolling past 30 files from a parent branch that haven't been reviewed yet.
Step 4: Handle Review Feedback Without Losing Your Mind
Here's where the tooling actually pays off. When a reviewer requests changes on an early PR in the stack, you make the fix on that branch and then sync the rest:
# Fix something in the first branch
git checkout refactor/extract-auth-service
# ... make the requested changes ...
git add -A && git commit -m "Address review: rename method for clarity"
# Restack everything above it
gh stack sync
The sync operation rebases each branch in the stack onto its updated parent and force-pushes the results. What used to be a five-minute manual juggling act becomes one command.
Step 5: Merge From the Bottom
When the bottom PR gets approved, merge it. The tooling automatically retargets the next PR in the stack to point at main (or whatever your default branch is). You work through the stack from bottom to top.
Why This Actually Works in Practice
I've been using stacked PR workflows across a few projects, and the difference in review quality is night and day. Here's what changes:
- Reviewers actually read the code. A 150-line PR with a clear description gets thoughtful feedback. A 1,200-line PR gets a rubber stamp.
- You catch design issues earlier. When you're forced to break work into logical layers, you naturally think about boundaries and interfaces upfront.
-
Merge conflicts shrink. Smaller branches that land faster mean less drift from
main. - Onboarding gets easier. New team members can review one slice of a feature instead of needing to understand the whole thing at once.
Common Pitfalls and How to Avoid Them
Don't stack too deep
Three to four PRs in a stack is the sweet spot. Beyond that, the cognitive overhead of tracking dependencies starts to outweigh the review benefits. If you need more than four layers, you might be working on something that should be broken into independent features instead.
Keep each layer genuinely independent-ish
Each PR in the stack should represent a coherent change that could theoretically be reviewed on its own. "Part 1 of the function" and "Part 2 of the function" is not a good split. "Data model" → "Business logic" → "API endpoint" → "UI" is.
Communicate the stack to reviewers
gh-stack automatically adds navigation links between related PRs, which helps. But also drop a comment on the first PR explaining the overall plan. Something like:
This is a 3-part stack implementing OAuth support:
- #101 — Extract auth service (this PR)
- #102 — Add OAuth providers
- #103 — OAuth selection UI
Watch out for CI
Each PR in a stack runs CI against its base branch, not main. This is usually what you want — it validates the incremental change. But make sure your CI is configured to handle non-main base branches correctly. Some setups assume every PR targets main and will break otherwise.
Alternatives Worth Knowing About
If gh-stack doesn't fit your workflow, there are other tools in this space:
- git-branchless — Takes a more radical approach, bringing a commit-centric workflow (inspired by Mercurial) to git. Great if you want to rethink how you use git entirely.
- Graphite — A popular third-party tool for stacked PRs with a slick CLI and web dashboard.
- Manual rebasing — Always an option. No dependencies, full control, maximum suffering.
The core concept is the same across all of them: break big changes into small, reviewable, dependent chunks and let tooling handle the bookkeeping.
Prevention: Build the Habit
The best way to avoid the giant-PR problem is to start thinking in stacks before you write code. When you pick up a ticket, spend five minutes sketching out the layers:
- What's the lowest-level change that makes sense on its own?
- What builds on top of that?
- Can any of these layers be independent (parallel) instead of stacked (sequential)?
Once you train yourself to decompose work this way, you'll find that your PRs naturally stay small — even without fancy tooling. The tools just make it painless to maintain the stack when reality doesn't match your plan.
And reality never matches the plan.
Top comments (0)