A large pull request often starts innocently.

A developer begins with a database change, adds the corresponding domain model, updates an API, changes the frontend, adds tests, and finally opens one pull request containing several hundred or several thousand lines of changes.

The code may be correct, but reviewing it becomes difficult. The reviewer has to understand several layers at once, distinguish foundational changes from dependent ones, and keep track of which parts are actually related to the requested feature.

The usual response is to split the work into multiple branches. That helps, but it introduces another problem: keeping those dependent branches synchronized and making sure the pull requests remain understandable as earlier changes are merged.

GitHub's stacked pull requests are designed for this situation. The feature is now generally available and allows developers to organize dependent changes as a chain of smaller pull requests that can be reviewed and merged independently.

The important idea is simple: large changes can be delivered as a sequence of small, reviewable changes without pretending that those changes are independent.

What Is a Stacked Pull Request?

A stacked pull request is a series of dependent pull requests where each branch is based on the branch immediately below it.

Suppose you are building a new order-management feature in a .NET application.

Instead of creating one large branch:

main
  |
  +---- order-management-feature
             |
             +-- database
             +-- domain
             +-- API
             +-- tests

you can structure the work as a stack:

main
  |
  +-- stack/database
          |
          +-- stack/domain
                  |
                  +-- stack/api
                          |
                          +-- stack/tests

Each layer contains a focused change.

The corresponding pull requests might be:

PR #1  Database schema
  ↓
PR #2  Domain model
  ↓
PR #3  API endpoints
  ↓
PR #4  Integration tests

PR #2 depends on PR #1, PR #3 depends on PR #2, and PR #4 depends on PR #3.

The key difference from ordinary pull requests is that the dependency relationship is explicit rather than hidden inside a large branch.

GitHub describes a stack as two or more pull requests in the same repository where each pull request targets the branch of the pull request below it.

Why Large Pull Requests Become a Problem

Large pull requests are not automatically bad.

A change may genuinely need to touch many files. The problem is that pull-request size increases the amount of information a reviewer must process before they can make a useful judgment.

Consider a change containing:

Database migration       80 lines
Domain model             120 lines
Service layer             250 lines
API controllers           300 lines
Tests                      600 lines
Configuration               50 lines
------------------------------------
Total                    1,400 lines

A reviewer may need to understand all of those changes before confidently approving the pull request.

That creates several risks.

The reviewer may focus on the most visible code and miss a subtle problem in the database migration. They may approve the implementation without fully understanding the test changes. Or the pull request may remain open long enough for main to move significantly ahead, creating merge conflicts.

Smaller pull requests reduce that cognitive load.

A reviewer can first verify the database layer, then the domain behavior, then the API, and finally the tests.

The changes are still connected, but the review surface is smaller.

Stacked PRs Solve the Dependency Problem

Normally, splitting a large feature into smaller pull requests becomes awkward when the changes depend on one another.

Suppose you have:

PR 1: Add Order entity
PR 2: Add OrderService
PR 3: Add OrderController

OrderService cannot compile without the Order entity.

Likewise, the controller depends on the service.

With ordinary branches, developers may have to wait for PR 1 to merge before starting PR 2, then wait for PR 2 before opening PR 3.

That slows development.

A stack allows the developer to continue working:

main
  |
  +-- order-entity
          |
          +-- order-service
                  |
                  +-- order-api

The next change can be developed before the previous change reaches main.

That is the central productivity benefit.

A Practical C# Example

Imagine you are adding customer address support to an ASP.NET Core application.

The complete feature might require changes to:

Customer entity
Customer database mapping
Customer service
Customer API
Validation
Integration tests

Instead of putting everything into one branch, create a stack.

Layer 1: Domain Model

public sealed class CustomerAddress
{
    public int Id { get; set; }

    public int CustomerId { get; set; }

    public required string Street { get; set; }

    public required string City { get; set; }

    public required string PostalCode { get; set; }
}

This pull request establishes the model.

Layer 2: Persistence

The next branch adds the Entity Framework Core configuration and migration.

public sealed class CustomerAddressConfiguration
    : IEntityTypeConfiguration<CustomerAddress>
{
    public void Configure(
        EntityTypeBuilder<CustomerAddress> builder)
    {
        builder.HasKey(x => x.Id);

        builder.Property(x => x.Street)
            .HasMaxLength(200)
            .IsRequired();

        builder.Property(x => x.City)
            .HasMaxLength(100)
            .IsRequired();

        builder.Property(x => x.PostalCode)
            .HasMaxLength(20)
            .IsRequired();
    }
}

This change depends on the first layer.

Layer 3: API

The next branch adds the endpoint:

[HttpPost("{customerId:int}/addresses")]
public async Task<IActionResult> AddAddress(
    int customerId,
    CreateCustomerAddressRequest request,
    CancellationToken cancellationToken)
{
    var address = await _customerService.AddAddressAsync(
        customerId,
        request,
        cancellationToken);

    return CreatedAtAction(
        nameof(GetAddress),
        new { customerId, addressId = address.Id },
        address);
}

The API layer depends on the domain and persistence changes.

Layer 4: Tests

The final layer adds integration tests for the complete workflow.

Each pull request now has a meaningful purpose.

A reviewer does not have to understand the entire feature to review the first layer.

The Stack Is a Dependency Graph

Although the UI presents a stack as a sequence, developers should think about it as a dependency graph.

For example:

                 main
                  |
            database layer
                  |
            domain layer
                  |
             API layer
              /     \
        unit tests   integration tests

The lower layer provides the foundation for the layers above it.

That means the order of the stack matters.

A useful rule is:

A change should only depend on changes in the same branch or a branch lower in the stack.

If the API branch depends on a database change that exists only in a completely separate branch, the stack no longer represents the real dependency relationship.

This is where experienced Git users will recognize an important concept: stacked pull requests are not magic. They make a branching strategy explicit, but the developer still has to structure dependencies correctly.

Creating a Stack with GitHub CLI

GitHub provides the gh stack extension for working with stacks from the command line. The workflow can begin by initializing a stack:

gh stack init customer-address

After implementing and committing the first layer:

git add .
git commit -m "Add customer address model"

you can create the next branch:

gh stack add customer-address-persistence

Then implement the persistence layer and commit it:

git add .
git commit -m "Add customer address persistence"

The next layer can then be added:

gh stack add customer-address-api

GitHub's CLI workflow handles the branch relationship so that the branches remain ordered as a stack.

The exact commands available depend on the installed gh stack extension version, so teams should keep their GitHub CLI tooling current.

What Happens When the Bottom PR Changes?

This is where stacked branches become significantly more complicated than simply creating several branches.

Suppose the stack looks like:

main
  |
  +-- PR 1
       |
       +-- PR 2
            |
            +-- PR 3

Now main receives another commit.

PR 1 must be rebased against the updated main.

If PR 2 and PR 3 were ordinary independent branches, developers would have to manually update each branch.

With stacked pull requests, GitHub provides cascading rebase support so changes higher in the stack can remain aligned with the updated base. GitHub also introduced improvements around rebasing as part of general availability.

That automation is important because branch maintenance is one of the biggest reasons teams avoid stacked workflows.

Approvals and Rebasing

One subtle problem with pull requests is that a rebase can invalidate the review state.

Imagine a reviewer approves PR 2.

Then PR 1 changes, causing PR 2 to be rebased.

If the code represented by PR 2 itself has not changed, forcing the reviewer to approve the same code again creates unnecessary friction.

GitHub's current stacked pull request behavior preserves approvals for unchanged code when a stack is rebased after its base branch moves forward, including repositories that dismiss stale approvals.

This distinction matters.

The system should differentiate between:

Base changed

and:

Reviewed change changed

Those are not necessarily the same event from a review perspective.

Signed Commits and Repository Rules

Enterprise repositories often require signed commits or enforce branch rules.

A stacked workflow therefore cannot simply rewrite commits without considering those policies.

GitHub's current implementation creates signed replacement commits during stack rebases, preserving original authorship, including cases where branch rules require signed commits or the original commit was signed.

That is an important production detail because teams frequently discover that a branching workflow works perfectly until it encounters repository governance rules.

A development workflow should be evaluated against the actual repository policies, not just the basic Git commands.

Merge Queues and Stacks

Merge queues become particularly interesting with stacked pull requests.

Suppose four pull requests represent one feature:

PR 1
 ↓
PR 2
 ↓
PR 3
 ↓
PR 4

Treating those as unrelated merge-queue entries could break the dependency chain.

GitHub's general-availability implementation keeps a stack together in the merge queue as a single merge group rather than splitting the stack across separate groups.

This is important because the purpose of a stack is to preserve the relationship between changes while still allowing each layer to be reviewed separately.

Stacked Pull Requests and AI Coding Agents

This feature becomes especially relevant as AI coding agents produce more code.

An agent may complete several logically separate tasks during one larger development effort.

Without a structured review strategy, that can result in one large pull request containing many unrelated or loosely related modifications.

A stack provides a natural structure:

Agent task 1
    ↓
PR 1

Agent task 2
    ↓
PR 2

Agent task 3
    ↓
PR 3

Each task remains independently reviewable while retaining the dependency relationship.

This is particularly useful for large refactoring work.

An agent could first introduce an abstraction, then migrate one subsystem, then migrate another subsystem, with each stage represented by a separate pull request.

The important caveat is that automation increases the need for review discipline. Producing more pull requests does not automatically produce better software.

Common Mistakes

Making the stack too deep

A stack of two or three focused pull requests can be easy to understand.

A stack containing fifteen tightly dependent branches can become difficult to navigate and maintain.

If every layer depends on every previous layer, a small change near the bottom can cause substantial rebasing work above it.

Use stacks to create meaningful review boundaries, not to turn every commit into a pull request.

Mixing unrelated work into one layer

A pull request should still have a clear purpose.

This:

PR 2:
- Add persistence
- Rename controllers
- Upgrade logging
- Change authentication

does not become a good pull request merely because it is smaller than the original feature branch.

The same review principles still apply.

Creating circular dependencies

A stack must have a clear direction.

If the API layer requires the test layer and the test layer requires changes from the API layer, the developer should reconsider the design rather than trying to force both changes into the same dependency chain.

Forgetting that the stack is still one feature

Small pull requests can create a false sense of independence.

A reviewer should understand the individual layer, but the team should also verify that the complete stack produces the intended behavior.

When Stacked Pull Requests Work Well

Stacking is particularly useful when a feature naturally decomposes into layers.

Good examples include:

It is less useful when changes are genuinely independent.

If two features do not depend on one another, separate ordinary pull requests are usually simpler.

Advantages and Disadvantages

Advantages

Smaller review surfaces: Each pull request contains a focused change, making it easier for reviewers to reason about the code.

Parallel development: Developers can continue building on unmerged work instead of waiting for every dependency to reach main.

Explicit dependencies: The branch structure communicates which changes must exist before another change can be understood or merged.

Better support for large changes: A feature can be delivered incrementally without creating one enormous pull request.

Useful for agentic development: AI-generated changes can be organized into smaller reviewable units instead of arriving as a single large diff.

Disadvantages

Branch management is more complex: Developers need to understand the relationship between the branches in the stack.

Changes near the bottom can affect everything above them: A foundational change may require cascading rebases.

Deep stacks become difficult to reason about: The more layers a stack contains, the more dependency management becomes part of everyday development.

Not appropriate for unrelated changes: Independent work does not benefit from artificial dependency chains.

Tooling matters: Teams need compatible GitHub and Git tooling, and some clients may not support the complete stacked workflow. GitHub currently does not support stacked pull requests in GitHub Desktop.

A Practical Workflow for .NET Teams

For a medium-sized .NET feature, a reasonable stack might look like:

main
 |
 +-- 01-domain-model
 |
 +-- 02-ef-core-persistence
 |
 +-- 03-application-service
 |
 +-- 04-api-endpoints
 |
 +-- 05-integration-tests

Keep each layer independently buildable where practical.

Run the relevant tests at each stage.

Keep commits focused.

Avoid putting unrelated cleanup into a lower branch simply because it happens to be convenient.

Most importantly, communicate the stack structure to reviewers. A reviewer should know which pull request to start with and why the next pull request depends on it.

Summary

GitHub stacked pull requests provide a structured way to break large, dependent changes into smaller pull requests without forcing developers to wait for every change to merge before starting the next one.

The underlying idea is simple: build a feature as a sequence of dependent branches, with each pull request representing a focused layer of work.

The engineering benefit comes from reducing review complexity while preserving development momentum. GitHub's current implementation goes beyond branch relationships by handling important operational details such as cascading rebases, approval preservation for unchanged code, signed replacement commits, automatic stack retargeting, merge-queue grouping, and improved CLI and agent workflows.

For developers, the best use of stacked pull requests is not to create more pull requests for the sake of it. The goal is to make a large change easier to understand, review, test, and merge.

When a feature has real dependencies and a large diff would otherwise become a review bottleneck, stacking can turn one difficult pull request into a sequence of smaller changes that still preserve the architecture and dependency order of the overall implementation.