GitHub Stacked Pull Requests: A Practical Review Workflow


Large changes create an awkward choice.

Open one pull request and the reviewer receives a wall of code. Split the work into independent pull requests and later pieces cannot start until earlier ones merge. Create dependent branches by hand and the code becomes easier to review, but keeping every branch, base, and pull request synchronized becomes a job of its own.

Stacked pull requests are the attempt to escape that choice.

GitHub has now put the workflow into public preview across its web interface, mobile apps, APIs, merge system, and a new gh stack CLI extension. The core idea is old: break one large change into a sequence of small, ordered changes. The important change is that GitHub now understands the sequence as one object instead of displaying several mysteriously related pull requests.

This is more than a tidier branch diagram. A good stack turns an implementation into a reviewable argument. Each layer should establish one fact the next layer can safely depend on.

What a Stack Actually Is

A stack is an ordered chain of branches and pull requests. Only the first branch targets the repository’s trunk. Every later branch targets the branch immediately below it.

Imagine an authentication feature divided into three layers:

main
└── auth-model          → PR #101, base: main
    └── auth-api        → PR #102, base: auth-model
        └── auth-ui     → PR #103, base: auth-api

The bottom pull request introduces the data model. The middle pull request adds endpoints that depend on that model. The top pull request builds the interface that consumes the endpoints.

When a reviewer opens PR #102, the relevant comparison is auth-api against auth-model. The model work is not repeated in the diff. PR #102 can therefore answer one narrow question: is the API layer correct?

That distinction is why a stack is not merely a pull request with carefully arranged commits.

Commits help explain history inside one proposal. Pull requests carry review state: a description, requested reviewers, code-owner rules, discussion threads, approvals, checks, draft status, and a merge decision. Giving each layer its own pull request gives each logical change its own review boundary. A database specialist can review the model layer while an application team reviews the API layer, and neither has to approve an ambiguous bundle.

The Real Problem Is Waiting, Not Branching

Developers have always been able to branch from an unmerged branch. The Git commands are not new. The trouble begins when the bottom layer changes.

Suppose review feedback requires rewriting auth-model. The auth-api branch still contains the old model commit in its ancestry, and auth-ui contains both old lower layers. To keep each diff accurate, the author must update the bottom branch, rebase every branch above it in order, force-push safely, and verify that each pull request still targets the right base.

Without tooling, the process looks roughly like this:

git switch auth-api
git rebase auth-model

git switch auth-ui
git rebase auth-api

Add five or ten layers, remote edits, merge conflicts, and multiple authors, and the maintenance cost becomes obvious. This is why stacked workflows have often depended on Gerrit, Phabricator, Graphite, Sapling, or internal tools rather than ordinary GitHub pull requests.

GitHub’s public preview does not invent dependency chains. It makes the platform aware of them and automates much of the bookkeeping.

Starting with gh stack

The CLI extension installs through the normal GitHub CLI:

gh extension install github/gh-stack

Inside a repository, initialize a stack and create the first layer:

gh stack init

After implementing and committing that layer, add another branch:

gh stack add auth-api

Continue until the change has the right shape, then publish the branches and pull requests:

gh stack submit

The extension records local stack metadata in .git/gh-stack, so it is local repository state rather than a file committed with the project. It creates one pull request per branch and sets each pull request’s base to the branch below it.

Several commands cover the day-to-day loop:

  • gh stack view shows the order and state of the stack.
  • gh stack up, down, top, and bottom move between layers.
  • gh stack checkout <pr-number> fetches an existing remote stack and reconstructs it locally.
  • gh stack rebase cascades a rebase from trunk through the dependent branches.
  • gh stack push publishes updated branches.
  • gh stack sync fetches, rebases when needed, pushes, and refreshes pull request state.
  • gh stack modify opens a terminal interface for reordering, renaming, dropping, or folding layers.

The rebase command also supports --continue and --abort. That matters because a cascading rebase does not eliminate conflicts. It gives the conflict process a stack-wide transaction: pause at the broken layer, resolve it, continue upward, or restore the branches to their earlier state.

The right mental model is not “the tool makes Git disappear.” The tool applies the same ancestry rules consistently across every branch.

Review the Story from the Bottom Up

A stack should be read in dependency order.

The lowest layer creates the first stable premise. It might be a mechanical rename, a schema migration, a new interface, or a refactor that preserves behavior. The next layer uses that premise. Each later layer should add one meaningful capability without silently changing the assumptions below it.

GitHub displays a stack map on each pull request so reviewers can see where the current layer sits. Reviews and checks still belong to individual pull requests, and existing branch protections continue to guard the path to main.

This permits parallel review, but parallel does not mean unordered.

A reviewer can inspect the UI layer before the model layer is approved, especially when the layers have different owners. However, approval of the top layer is conditional on the lower contract remaining stable. If the bottom layer changes its API, every reviewer above it needs to know whether the change invalidates an earlier conclusion.

Teams should make the dependency explicit in every pull request description:

  • what this layer establishes
  • which lower layer it assumes
  • what later layers depend on it
  • whether it can ship independently
  • where reviewers should begin

That context is much more useful than titles such as “part 2” and “part 3.” A stack map shows order; it does not explain intent.

How to Cut a Useful Stack

The hardest part is not the CLI. It is deciding where one review unit ends and the next begins.

A layer is well cut when it has one reason to change, a coherent test surface, and a conclusion a reviewer can approve. Good boundaries often follow this sequence:

  1. Mechanical preparation. Rename a type, move a module, or isolate an interface without changing behavior.
  2. Foundation. Add the schema, domain model, protocol, or shared abstraction.
  3. Behavior. Implement the service or application logic that uses the foundation.
  4. Integration. Connect the behavior to an API, event stream, job, or user interface.
  5. Rollout. Add a feature flag, migration step, telemetry, or cleanup.

That order keeps conceptual changes separate from noisy mechanical changes. A reviewer should not have to prove that a renamed function still behaves the same while also evaluating new authorization logic.

Every layer does not need to be deployable by itself, but it should leave the repository in a valid state. It should compile, pass the relevant checks, and preserve established invariants. If a layer intentionally introduces dormant code, say how the later activation layer will expose it.

Avoid cutting a stack by file count alone. A pull request containing “all backend files” can still mix several concerns, while a cross-cutting API rename across many files may be one perfectly coherent mechanical layer.

The test is simple: can a reviewer describe what this layer proves in one sentence?

Merging the Whole Stack

GitHub’s native merge behavior is the most significant platform addition.

When the latest ready pull request is merged as a stack, GitHub can land it together with every unmerged layer below it. The operation respects the existing required reviews, status checks, branch protections, and merge requirements for the individual pull requests.

Teams can also land only the lower portion. GitHub says the remaining upper pull requests stay open and are automatically rebased and retargeted after the lower layers merge.

That matters for deployment pipelines. GitHub’s stack merge API is asynchronous because a multi-layer merge can take longer than the timeout used by the older synchronous path. A complete stack can be applied atomically with one push to the base branch, allowing CI or deployment automation to react to the final combined state instead of processing every layer as a separate trunk update.

GitHub also exposes REST and GraphQL stack objects. The REST API can list and retrieve stacks, create a stack from an ordered set of existing pull requests, append pull requests, or remove unmerged pull requests from a stack. The documented maximum is 100 pull requests in one stack, although a stack that deep would be a serious human-factors problem long before it reached the API limit.

Native APIs are important for teams with custom merge queues, release bots, or policy engines. A first-class stack cannot stop at the web interface; automation needs to know that PR #103 depends on #102 and #101 rather than inferring relationships from branch names or description links.

Public Preview Means Preview

The feature is promising, but teams should adopt it with the caution appropriate to a public preview.

The launch discussion includes reports of edge cases around whole-stack squash merges, stale local and remote branches, deleted base branches, and approval rules after rebases. A GitHub engineer explained that squash-merging a stack is unusually difficult because the platform must predict a sequence of commits that do not yet exist, then evaluate mergeability, protection rules, and approvals against those future results.

GitHub reported in that discussion that 99% of stack merges were succeeding, while also calling further reliability work a top priority. That is a healthy number for experimentation and an uncomfortable number for a critical workflow that runs hundreds of times.

Cross-fork stacks are another boundary. GitHub said support is planned for a stack contained inside one contributor fork and targeting the original repository. More complicated stacks spanning multiple forks raise security problems because the platform automatically rebases branches after partial merges.

Merge queue support is also rolling out progressively rather than appearing everywhere at once.

Practical safeguards are straightforward:

  • begin with two or three layers, not ten
  • use the merge strategy your repository already understands
  • keep local branches synchronized before starting a cascade rebase
  • inspect every pull request after conflict resolution
  • retain ordinary branch protection and required checks
  • test partial and full-stack merging in a low-risk repository first
  • document how approvals are handled after a lower layer changes

The stack UI reduces invisible relationships. It does not remove the need to understand Git ancestry.

Small Pull Requests Are Not Automatically Fast Pull Requests

Stacking is often sold with a simple formula: smaller pull requests merge faster. The research is less tidy.

A large empirical study covering 845,316 GitHub pull requests, plus data from Gerrit and Phabricator, found no strong relationship between pull request size and time to merge. Review latency depends on far more than lines changed: reviewer availability, organizational boundaries, change type, risk, CI duration, contributor trust, and release policy all matter.

That result does not make stacks useless. It clarifies their value.

The best reason to stack is not to manufacture a better velocity metric. It is to improve the unit of reasoning. A focused pull request lets a reviewer understand intent, select the right expertise, discuss one concern, and approve a bounded claim. It also makes an individual layer easier to revert or diagnose.

A badly designed stack can be slower than one large pull request. It creates more notifications, descriptions, check runs, branch updates, and approval decisions without reducing cognitive load. Splitting 600 tangled lines into six arbitrary 100-line slices is paperwork, not architecture.

The stack pays for itself when the layers expose the real dependency structure of the change.

Stacks in the Age of Coding Agents

Coding agents make this workflow more relevant and more dangerous.

They can produce changes faster than humans can review them. Asking an agent for an entire feature can leave a maintainer facing thousands of lines that arrived in minutes but still require careful human judgment. A stack can turn that output into a sequence: preparation, data model, service behavior, integration, and rollout.

Agents can also perform the mechanical work of proposing boundaries, creating branches, updating descriptions, and cascading rebases. GitHub even provides a gh-stack skill intended for coding agents.

But an agent should not choose boundaries only to satisfy a size limit. The author remains responsible for the argument encoded by the stack:

  • Does every layer have one clear purpose?
  • Are tests introduced with the behavior they validate?
  • Is a security-sensitive change isolated for the right reviewer?
  • Does an early refactor genuinely preserve behavior?
  • Can later layers be understood without hiding important interactions?

The volume of generated code makes review structure more important. It does not make review optional.

A Team Adoption Checklist

Before making stacks the default for large work, agree on a few rules.

Define when to stack. A two-file bug fix does not need a branch hierarchy. Use stacks for dependent work that would otherwise become one difficult review or a sequence of blocked changes.

Set a depth guideline. Three to five layers are usually understandable. A deeper stack may signal that the feature should be divided into separate deliverables.

Require coherent layers, not line limits. Size is a signal. Purpose is the boundary.

Review from the bottom. Later reviews can begin early, but lower-level contracts must settle first.

Name branches and pull requests by capability. Prefer auth-model and auth-api over feature-1 and feature-2.

Keep checks attached to every layer. A stack is not permission to postpone correctness until the top.

Plan for changes low in the stack. Decide who rebases, when reviews are dismissed, and how reviewers are notified of changed dependencies.

Measure outcomes, not PR count. More pull requests are an implementation detail, not proof of more productivity.

The Bottom Line

GitHub’s stacked pull requests make a mature code-review pattern available without forcing teams to bolt a second review system onto their repository. The platform can now display dependencies, preserve focused diffs, coordinate existing checks and protections, expose stack APIs, and merge several approved layers as one operation.

The workflow succeeds only when the stack tells a clear technical story.

Start with the smallest stable premise. Put one reviewable idea in each layer. Keep every layer valid. Make dependencies explicit. Treat rebases as real history rewrites, not magic. And remember that a stack is valuable because it helps reviewers reason—not because it creates more pull requests.

Used that way, stacked pull requests can let implementation continue without turning review into an afterthought.

Sources

100%