Originally published at kunalganglani.com — read it there for inline code, hero image, and live links.
Stacked PRs on GitHub are the fastest way I know to stop shipping-by-mega-PR without turning your team into a process cult. If you’re here for how to do stacked PRs on GitHub, you probably have the same two problems every team hits: review fatigue (nobody wants your 1,800-line diff) and developer blocking (you’re “done” but can’t start the next slice until main merges).
Key takeaways
- Stacked pull requests work when every PR is reviewable alone, even if it can’t ship alone.
- A simple naming + title convention is 80% of the battle for dependent pull requests GitHub workflows.
- Changing a PR’s base branch is powerful, but it can make comments outdated and even drop commits from the timeline.
- CI for stacked PRs should be tiered (smoke per PR, full suite once per stack), or you’ll pay N× for the same signal.
- Merge the stack bottom-up, and have a restack routine ready for conflicts and base-branch drift.
Stacked PRs aren’t “more process”. They’re the same work, sliced so humans and CI can actually keep up.
The Problem with Large Pull Requests
A big PR is a tax you pay in three currencies: reviewer attention, CI time, and your own momentum.
I don’t care how good your team is. Once a diff gets huge, the odds of a serious review drop hard. People start skimming. They defer it. They ask for “quick calls” because reading it properly is too much. Then you end up shipping vibes, not software.
Tomas Reimers (Co-founder/CEO at Graphite) uses a deliberately painful example: a single PR with ~2,000 lines added. The point isn’t that 2,000 lines is always wrong. The point is that it’s predictably less likely to be reviewed well, or reviewed at all. That “I’ll look later” PR becomes the load-bearing wall for your whole feature.
Then you do one of three things, none of them good:
- You ping reviewers until you feel annoying (because you are).
- You context-switch to something else and lose the thread.
- Or you keep piling commits onto the same branch until the diff turns into a horror movie.
Stacked PRs exist because the classic “branch off main only” mental model is mostly habit. Git doesn’t require it. GitHub doesn’t require it. Teams just got trained into it.
GitHub making stacked PRs a first-class concept in docs (create/manage/review/CI/troubleshoot/merge) matters because it stops being tribal knowledge. You can point to a canonical workflow and say, “No, this isn’t some weird personal preference. It’s supported.”
Here’s the contrarian part: the mechanics are easy. What’s hard is hygiene. Without hygiene, stacked PRs just give you five smaller messes instead of one big mess.
(Inline illustration suggestion: “One huge PR vs. 4 stacked PRs” diagram.)
Stacked Pull Requests (and what they actually are)
Stacked pull requests are multiple pull requests where each PR’s base branch is the PR below it, creating a dependency chain like:
main <- PR1 <- PR2 <- PR3
Instead of one branch containing every commit for a feature, you split the feature into layers. Each layer is small enough to review and reason about. The stack still represents one coherent feature, but reviewers can approve incrementally.
What are stacked pull requests?
They’re dependent PRs where the “compare against” branch is not main, but another feature branch. GitHub shows each layer’s diff as “what changed since the base layer”. That’s the whole point.
This is why people who’ve used Gerrit/Phabricator feel at home here. You’re reviewing a sequence of changes, not a monolith.
The simplest branch naming + PR title convention
I’m opinionated here because ambiguity kills stacks. If your naming is sloppy, you force everyone to keep the whole stack in their head. That defeats the point.
Branch naming (copy-paste)
feat/<ticket>-<feature>/01-foundationfeat/<ticket>-<feature>/02-domainfeat/<ticket>-<feature>/03-apifeat/<ticket>-<feature>/04-ui
Example:
feat/123-comments/01-foundationfeat/123-comments/02-storagefeat/123-comments/03-endpointsfeat/123-comments/04-ui
PR title format (copy-paste)
[1/4] Comments: foundation[2/4] Comments: storage[3/4] Comments: endpoints[4/4] Comments: UI
Why numbers? Because humans scan lists. Also, Slack links become readable. If you’ve ever tried to track “Comments PR” vs “Comments PR (follow-up)” vs “Comments PR final-final”, you know the pain.
PR description template (copy-paste)
- Stack: # → # → # → #
- Depends on: #
- Unblocks: #
- Scope: What this PR includes (2–4 bullets)
- Non-goals: What this PR explicitly does not include
- Review notes: “Start with Files changed. Ignore generated files. Suggested order: X then Y.”
Add a label like stacked and optionally stack:<ticket> so it’s filterable.
Internal links you might want while you’re here:
- If your team is already fighting review overload, my policy write-up on code-review is the same problem from the other side.
- If your CI is already expensive, you’ll recognize the same pattern as eval gating in CI/CD.
GitHub PR mechanics that matter for stacks
This is where GitHub-native stacks differ from the muscle memory you might have from Gerrit or Phabricator.
Draft pull requests
Use Draft PRs aggressively for the top of the stack.
Rule of thumb:
- Bottom PR (
[1/4]) should be Ready for review as soon as it has tests and a clean story. - Everything above it starts life as a Draft pull request until its base is stable.
Drafts matter because they set expectations. They tell reviewers: “you can start looking, but don’t waste time trying to reason about CI failures that are just upstream churn.” GitHub supports drafts as a first-class state, and it cuts down on the pointless back-and-forth.
Pull request refs and merge branches
GitHub doesn’t just run checks on your head branch. It can create a synthetic merge ref (often described as a “merge branch”) representing “what would happen if we merged this PR into its base right now”. That’s the check you actually care about.
For stacked PRs, this is non-negotiable because the base branch is itself changing.
Your checks should answer:
- Does PR3 work when merged into PR2’s branch as it exists today?
- Or are we green only on the head branch while the merge result is broken?
If you use GitHub Actions required checks, you’re effectively requiring checks on that merge result.
Differences between commits on compare and pull request pages
GitHub shows commits differently depending on whether you’re looking at a compare view or the PR view. In stacks, this confuses people because commits can “move” after rebases/restacks.
The fix is cultural, not technical: make review about diffs and intent, not commit archaeology.
Practical guidance:
- Optimize for Files changed and a tight PR description.
- Treat commit order as a convenience, not a contract.
Collaborative development models
Stacked PRs fit best with a “feature branch + PR” model, but they also work in trunk-based development if you treat each layer as a short-lived branch.
The constraint is social, not technical: you’re asking reviewers to approve incremental work. If your team insists that every PR must be independently shippable and perfectly polished, stacks will feel “wrong”. If your team can handle “reviewable and correct, but not deployable alone”, stacks are great.
GitHub’s foundational PR docs are worth reading once (not every week): About pull requests.
(Inline illustration suggestion: “PR tabs that matter: Conversation vs Checks vs Files changed”.)
How to create dependent pull requests on GitHub (UI + CLI)
This section is intentionally “do this now.”
UI workflow (GitHub-native)
- Create bottom branch from
main:feat/123-comments/01-foundation. - Push and open PR1 targeting
main. - Create PR2 branch from PR1 branch, not from
main:feat/123-comments/02-storage. - Push and open PR2 targeting PR1 branch as the base.
- Repeat for PR3/PR4.
If you do only one thing right: always branch from the previous layer.
If you branch PR3 from main “because it’s easier”, you didn’t build a stack. You built parallel PRs that will fight each other.
Can you stack pull requests with GitHub CLI?
Yes. GitHub CLI (gh) doesn’t magically “manage stacks” for you, but it supports the mechanics just fine.
A minimal flow:
- Use git locally to create branches off branches.
- Use
gh pr createfor each branch. - Ensure the
--basepoints at the branch below.
I’m not putting a giant command wall here because teams have different defaults and hooks. But the only two commands you need to remember are:
gh pr create --base <base-branch> --head <head-branch>gh pr edit --base <new-base-branch>
If you want a repeatable CLI environment for this kind of work, build muscle memory around clean tooling like direnv + mise so your git hooks, linters, and CI parity stay consistent.
How do you change the base branch of a pull request?
GitHub makes this a UI action and a CLI action, but it comes with sharp edges.
GitHub explicitly warns:
- “When you change the base branch of your pull request, some commits may be removed from the timeline.”
- “Review comments may also become outdated because the line of code that the comment referenced may no longer be part of the changes.”
Source: Changing the base branch of a pull request.
Also: when you open a PR, GitHub pins the base to the commit that branch references at that moment. If the base branch gets new commits later, GitHub does not automatically update the base commit for your PR. That’s why stacks drift and suddenly your “green” PR isn’t really green anymore.
Practical playbook:
- Only change base branches when you’re restacking after a merge or conflict.
- When you change a base, leave a comment: “Restacked onto
<branch>; some threads may be outdated.” - Expect reviewers to lose some context. Plan for it.
Rebase vs merge: what to do with stacked PR branches
Should stacked PR branches be rebased or merged?
My stance: rebase locally, merge on GitHub. Keep the PR diff clean, keep main history sane, and don’t rewrite history other people are depending on.
The canonical warning comes from Scott Chacon and Ben Straub in Pro Git: rebasing rewrites history and is dangerous on branches other people base work on. In stacks, that’s literally the point. Other branches are based on your branch.
Source: Scott Chacon and Ben Straub.
So the real rule is:
- If your stack is only you, rebasing to keep diffs tight is fine.
- If a teammate is building on top of your stack branch, treat that branch as public. Avoid rebasing it. Use merges or coordinate like adults.
Concrete guidance:
- Bottom PR (PR1): avoid force-pushing after review starts unless absolutely necessary.
- Top PRs (PR3/PR4): rebase is more tolerable early, because fewer people reviewed and fewer branches depend on them.
What happens to reviews when you rebase a stacked PR?
Threads go stale. Approvals can be dismissed depending on branch protection settings. And even when GitHub keeps the “Approved” state, the human reality is that reviewers feel like they’re re-reviewing the same thing.
The mitigation isn’t “never rebase.” The mitigation is discipline:
- Keep each layer small (think 100–300 lines net change, not a law but a useful target).
- Rebase early, not late.
- When you must rewrite history, do it once, do it cleanly, and tell people.
CI for stacked PRs with GitHub Actions (without paying N×)
How do you keep CI green with stacked PRs?
You need to decide what “green” means at each layer.
If you run the full suite on every PR in a 6-PR stack, you’ve built a CI cost multiplier. It’s not theoretical. It’s your cloud bill and your queue time.
GitHub Actions gives you the primitives: the pull_request event and filters (branches, paths) to decide when workflows run.
Source: Events that trigger workflows – pull_request.
Here’s a strategy that actually holds up in practice.
The tiered CI playbook (copy-paste policy)
Per-PR required checks (fast, deterministic)
- Lint + typecheck
- Unit tests for changed packages
- A “smoke” integration test that runs in <10 minutes
These should be required on every PR in the stack, because they’re your “merge safety belt.”
Per-stack full checks (slow, expensive)
- Full integration suite
- E2E tests
- Performance or load tests
Run these:
- On PR1 only (the bottom), or
- Nightly on the stack label, or
- On merges to
main
If you’re doing modern merge discipline, consider also running full checks on merge queue / merge group. Just don’t make every single PR pay for the entire test matrix.
Preventing CI duplication in practice
You have three levers:
- Path filters: don’t run backend integration tests when only docs changed.
- Concurrency: cancel in-progress runs when a new commit is pushed to the same PR.
- Required checks policy: only require the smoke suite on stacked PRs; require full suite on merge-to-main.
This is the same “regression gates” mindset I use in AI eval pipelines: cheap checks on every change, expensive checks on fewer choke points. If that resonates, AI engineering eval gates is the same idea applied to prompts and tooling.
(Inline illustration suggestion: “CI tiers per PR vs per stack” flowchart.)
Review hygiene: how to keep reviewers sane
Stacked PRs fail when reviewers don’t know what to do with them.
And no, “just read them in order” isn’t enough. If you don’t set expectations, people will review PR4 like it’s PR1 and you’ll get philosophical debates in the UI layer. Every time.
Here’s the reviewer contract that works.
How should reviewers review a stack without re-reviewing the same diff multiple times?
Rule 1: Review each PR as a layer, not as a feature.
- PR1: “Is the foundation right?”
- PR2: “Is the domain/model correct?”
- PR3: “Is the API behavior correct?”
- PR4: “Is the UI correct?”
If someone comments “Why are we doing this?” in PR4, that’s a process smell. That belongs in PR1.
Rule 2: Don’t bikeshed naming in the middle of the stack.
If you rename a core type in PR3, you just created churn across PR4 and PR5. Either:
- Fix it in PR1/PR2, or
- Defer it to a follow-up.
Mid-stack churn is how stacks become slower than mega-PRs.
Rule 3: Approve with explicit dependency language.
Use a standard comment:
- “Approved assuming PR1 merges as-is.”
This gives the author permission to keep moving without pretending everything is independently shippable.
Rule 4: When a base changes, stop arguing with outdated threads.
If a thread is outdated, resolve it with a single note: “Restacked; this moved to PR2.” Then move on.
If your team is also adopting AI-assisted coding and getting PR floods, you’re going to want a policy anyway. Start here: AI coding team workflow policy.
Failure modes: restacking, conflicts, and merging safely
Stacks aren’t fragile. They’re just honest about dependency.
You’re going to hit conflicts. You’re going to have to restack. That’s normal. The failure mode is pretending you don’t need a playbook and then reinventing one in a panic every sprint.
How do you restack after the bottom PR merges?
When PR1 merges into main, every PR above it is now based on the wrong branch.
The clean GitHub-native approach:
- Update your local
main. - Update your local PR2 branch by rebasing/merging onto
main. - Push PR2.
- Change PR2 base branch on GitHub from PR1 branch →
main. - Repeat upwards: PR3 base becomes PR2 branch, PR4 base becomes PR3 branch.
This is where GitHub’s warning matters: changing base can make comments outdated and drop commits from the timeline. That’s not a reason to avoid it. It’s a reason to:
- Restack quickly after merges.
- Avoid restacking daily as a hobby.
How do you handle mid-stack changes that require modifying lower layers?
This is the classic: reviewer of PR4 finds an issue that should be fixed in PR2.
Do not patch it in PR4.
Do this instead:
- Make the fix on PR2 branch.
- Merge/rebase PR3 and PR4 on top of PR2.
- Leave a short comment in PR4: “Fix moved to PR2; restacked.”
Yes, it’s extra work. It’s still less work than shipping a tangled diff where the real behavior change is hidden in the wrong layer.
How do you merge stacked PRs in order safely?
Bottom-up. Always.
- Merge PR1 →
main. - Restack PR2 base to
main(or update it so it targetsmain). - Merge PR2.
- Continue.
If you use GitHub’s merge queue, stacks can work, but you need to be strict about required checks on each PR’s merge result.
Tooling: plain Git/GitHub vs GitHub CLI vs Graphite/Git Town
You don’t need paid tooling to do stacked PRs. You need discipline.
Here’s the decision table I’d use.
| Option | Best for | What you gain | What you pay | My take |
|---|---|---|---|---|
| GitHub UI only | Small teams, low churn | Zero setup | Manual base changes | Fine for stacks of 2–4 PRs |
GitHub CLI (gh) |
Engineers who live in terminal | Faster create/edit, scripting | Still manual restacking logic | Great default for most teams |
| Graphite / GitKraken | High churn, lots of stacks | Purpose-built stack ops, navigation | Cost + workflow lock-in | Worth it if stacks are daily life |
| Git Town | Teams that want structured branching | Consistent local branch ops | Learning curve | Solid if you commit to it |
If you want a good primer on why stacks exist and the “what if you didn’t have to branch off main?” framing, Tomas Reimers’ talk write-up is still useful context: Tomas Reimers.
Here’s the official talk video (worth 20 minutes):
[YOUTUBE:U8wJoxxmskM|Stacked Pull Requests | GitKon 2022 | Tomas Reimers, Graphite]
The 8-step stacked PR recipe (print this)
This is the part you can paste into your team wiki.
- Slice the feature into 3–6 layers (foundation → behavior → API → UI).
-
Create branches with numbered names (
01-…,02-…). -
Open PR1 targeting
main. Make it Ready for review. - Open PR2..PRn targeting the branch below. Keep them Draft until stable.
- CI tiering: require fast checks on every PR; run full suite once per stack or on merge.
- Reviewer rules: review the layer only; approve “assuming base merges”; avoid mid-stack churn.
- Merge bottom-up only.
- Restack after merges by updating bases (expect some outdated threads; communicate clearly).
If you do this consistently, the surprising outcome isn’t just faster reviews. It’s better design. When you’re forced to name and isolate layers, bad abstractions show up early.
A quick note on metrics
If you want to prove this to your org, track two numbers for a month:
- Median PR “time to first review”
- Median PR “time to merge”
If stacked PRs don’t improve those, your bottleneck isn’t PR size. It’s staffing or ownership.
Closing: stacked PRs are a culture change disguised as branching
GitHub making stacked PRs first-class is a hint: this workflow is no longer just a niche for teams that miss Phabricator.
The teams that win in 2026 won’t be the ones with the fanciest tooling. They’ll be the ones who can keep review quality high while shipping in thin slices.
My prediction: within a year, “stack hygiene” will become as normal as “write tests.” Not because it’s trendy. Because it’s the only way to scale review when output keeps going up.
If you try this workflow, be ruthless about one thing: don’t let stacks become an excuse to ship half-thought-through layers. Small PRs are not inherently good. Reviewable PRs are good.
Originally published on kunalganglani.com
Top comments (0)