Get started with stacked pull requests >
Turn one giant AI-generated pull request to a reviewable stack
Instead of one huge, un-reviewable pull request, teach coding agents to decompose work into a clean, ordered stack with GitHub stacked pull requests.
Think about the last big feature you shipped. Be honest. Did you cram it into one giant pull request, or did you split it into smaller scoped pull requests? For years, you have silently had to decide between watching a pull request grow so large that reviewing it becomes a nightmare or breaking it into a chain of smaller pull requests that you have to babysit, sync by hand, and untangle conflicts every time a change is introduced below.
Both options have trade-offs. One is hard to review, while the other is hard to maintain. Your decision that day leans towards the less painful option.
Now add coding agents. They are incredibly productive and are projected to drive a 50% productivity gain across every SDLC stage by 2028, according to Gartner. But, they can’t take away the choice of how you structure your pull requests. They amplify the need to make it.
In this post, follow along with an example of how you can use stacked pull requests to simplify reviews.
A closer look: Adding product search to a shopping assistant
Let’s say you issue a prompt to add product search to a shopping assistant, walk away and minutes later, literally, you come back to review, steer, and approve. But look closely at what tends to land in that single pull request:
- A new data model and its seed data
- An API route and its validation
- The client wiring and the UI and the empty/fallback/error states
…all of this and more in one ginormous 1,000+ line diff.
For agents largely trained on how code has traditionally been written over the years, this pattern is their default way of shipping. Let’s play this out.
You want to add product search on as existing web application and your starting state is:
- A mock AI Assistant showing responses from a random-line generator
- Inconsistent product data hardcoded and scattered across components
- No catalog module, no API, no data layer—no nothing
An issue is opened to implement the feature, and a typical flow would be to create a feature branch, assign it to a coding agent (or multiple custom agents), get a first draft of the whole implementation code and updated tests…
…you read the code (well, you maybe read the code). Then, you still need to manually verify feature behavior and make any necessary updates, push and open a pull request with its long-yet-shallow AI generated description, ensure CI checks are green, and self-review diff then request reviewers. You get started…
Reviewer: 1,721 lines changed!! This description isn’t very helpful. I’ll review this later.
And what follows is familiar:
- The large pull request becomes hard to review—so it just…sits there.
- Reviewers lose context and the feedback quality drops.
- It becomes even slower to merge.
This kicks off a manual, messy, time-consuming process that’s prone to conflicts before the feature lands, and it eventually lands under-reviewed.
GitHub stacked pull requests
Stacked pull requests introduce a different and better structure of delivery. The principle is simple: decomposition. Instead of shooting for a single pull request that addresses the issue in its entirety, you break down the feature into logical layers and identify the dependency chain to arrive at your desired goal. This gives you, and your agents, a native way to decompose work that otherwise lands in a giant pull request into a chain of small, focused and independently reviewable layers.
That large pull request that’s hard to review becomes a stack of smaller, logically ordered pull requests, each scoped to a single concern, small enough to hold in a reviewer’s head and with just enough context naturally flowing from the previously reviewed pull request.
Let’s make it happen.
The stack structure
Let’s look at the steps involved when decomposing the problem and arranging the layered stack.
First, and importantly, set the stack base. This matters because CI checks and merge rules throughout the stack management lifecycle get evaluated against the stack base.
Then, identify the core foundational unit of work and put it closer to the base (lowest in the stack), and layer dependent work above it.
| Stack Layer (L#)/Branch | What to ship | Depends on |
|---|---|---|
| L1 (feat/catalog-data) | A typed catalog with seed data, validation, and a data access module | main (stack base) |
| L2 (feat/search-api) | Validated /api/products/search endpoint | feat/catalog-data |
| L3 (feat/chat-grounding) | Chat calls the API and answers from real product data | feat/search-api |
| L4 (feat/grounded-ui) | Product citation cards + state | feat/chat-grounding |
Now the independent concerns are clear: data, API, wiring, UX, making it possible to allocate different reviewer audiences for each. Data is reviewed by a data owner, UX by a UI owner.
GitHub’s native support for stacked pull requests can be launched from the pull request UI and extends seamlessly to the terminal with the gh stack
CLI.
Install the stacked pull requests CLI extension
Run the following:
gh extension install github/gh-stack
In ancient times, you’d be set to start working. Not today though. There are agents working alongside you. These agents need to learn how stacks work and how to create and manage them on your behalf. The gh-stack skills
teaches them this.
gh skill install github/gh-stack
Or, if you prefer:
npx skills add github/gh-stack
For the specific feature from the above example, your development workflow has custom agents, each with defined work streams and that follow a strict scoping discipline to achieve the goal of small, single-scoped pull requests.
| Layer/branch | Agent |
|---|---|
| L1 (feat/catalog-data) | Data modeler agent |
| L2 ( feat/search-api) | Backend agent |
| L3 ( feat/chat-grounding) | Frontend agent |
| L4 ( feat/grounded-ui) | Frontend agent |
The last piece of the setup is to confirm CI exists. As mentioned earlier, each pull request will be evaluated against the stack base, and these checks will run for every layer.
Now the work begins.
Layer one: Data catalog foundation
Most agent workflows today are automated and execute autonomously in loops, but for the sake of illustration, we’ll cover each step at a time.
At this point, all agents are familiar with how stacked pull requests work, so a typical workflow at this stage would be:
- Invoking the Data Modeler agent with an appropriate prompt
- The agent initializes a new stack and sets the first branch—
feat/catalog-data
with main as its base usinggh init stack
- Checks out, works and runs validation
(All checks == green) ? commit the layer : Iterate
Reviewer’s note for the future: Are the types correct? Is the data validated? Is the query helper safe? Period.
Layer two: Product search API
Follow a flow similar to:
- Invoking the Backend agent with an appropriate prompt
- The agent adds the next layer
feat/search-api
on top of layer one, its base:feat/catalog-data
, to import the completed data access module withgh stack add
- Checks out, works and runs validation
- Developer tests the API manually
(API works && All checks == green) ? commit the layer : Iterate
Reviewer’s note for the future: Is input validated? Is the response contract stable? Are error/empty states handled here or pushed downstream? Period.
Layer three: Wire chat to the API
In this next layer, you:
- Invoke the Frontend agent with an appropriate prompt
- The agent adds the next layer
feat/chat-grounding
on top of layer two. Its base:feat/search-api
, which will branch off with both the data access module and validated API. - Checks out, works and runs browser tests with Playwright
(All checks == green) ? commit the layer : Iterate
Reviewer’s note for the future: Is every answer tracing back to a real API response? What happens when the API fails or returns nothing? Period.
Layer four: Grounded UI and citations
You’ll notice that layer three and layer four, despite having the same author, (Frontend agent), are layered distinctively. This is deliberate. The UI owner should not have to check the underlying data flow and vice versa, and this structure allows for that independence.
So, the frontend agent:
- Adds the next layer
feat/grounded-ui
on top of layer three, its base:feat/chat-grounding
- Checks out, works and runs browser tests with Playwright
(All checks == green) ? commit the layer : Iterate
Reviewer’s note for the future: Does every citation link back to a real product? Are loading, empty and error states all covered? Period.
Submit the stack
The four local stacked branches are ready. Next is to push them to remote with gh stack push
, then create pull requests linking them on GitHub with gh stack submit
.
The stack map and CI on each layer
Switching over to GitHub, all four pull requests are open and at the top of each one, you see a stack map, which is a one-click navigation system between pull requests in the stack.
Reviewing and updating the stack
Time to switch hats and look at a reviewer’s journey through stacked pull requests.
The stack map is a reviewer’s compass – a navigation aid between the top of the stack and its bottom, heading towards a successful merge. The movement is directional: read top-down, review bottom-up.
- Read top-down, for context. This gives you the end goal at the very beginning of the review process, so you can set a bearing. “Oh, so we want to display product cards on the chat interface.”
- Review bottom-up to build on the predetermined checkpoints. The implementation on each layer only makes sense once the preceding layer is understood.
You are no longer looking at a single 1,720+ line-sized pull request to be reviewed in one sitting, as we saw in our example, but instead, the review can be distributed in small, self-contained targets in a stack.
As the assigned human in the loop reviewer, you come in and look at layer one, the pull request at the bottom of the stack, and see that the automatic Copilot Code Review (CCR) caught two issues which you agree should be fixed.
Changes are requested at the bottom of the stack, so you:
- Hand the feedback to the layer one author, data modeler agent that owns the branch
- Suggestions are applied, tested, committed and pushed
- Once the fix lands on feat/catalog-data, the natural next question is: what does this mean for layers two, three, and four?
Since branch feat/catalog-data
was pushed out of turn after the review, GitHub flags it plainly: “Some branches in this stack have diverged and must be rebased” paired with “Unable to merge as a stack” flag and that blocks the merge.
Back on the pull request UI on GitHub, a one-click Rebase stack button appears. Before using the button, there is something important worth noting. Triggering a web-based rebase using this button runs it on GitHub’s servers, which means it resets the committer to whoever clicked the button, the resulting commits aren’t signed, and if branch protection expects signed commits, that one click quietly breaks.
The safer, equivalent move from the terminal would be gh stack rebase
to perform that same cascading rebase locally as you interactively resolve conflicts, but this time using your own Git configuration, then gh stack push
.
Finally, you’ll propagate through the stack. The rest of the stack, both local and on GitHub, now needs to catch up, and it couldn’t be easier than a single sync command gh stack sync
.
An all-in-one flow starts with fetching from origin, cascading a rebase of every branch above feat/catalog-data
onto the new commit, pushes the rebased branches and syncs pull request state from GitHub. This way, the change ripples upward without anyone touching layers two, three, or four by hand.
Back on GitHub, all checks re-run, pass and the stack map settles back into a clean, mergeable line from main to feat/grounded-ui
.
Tags:
Written by
Related posts
Don’t stop early: Case-folding source code at memory speed
How a branch-free loop and byte-space arithmetic let GitHub case-fold every byte of code search at >45 GiB/s on a single core.
Tame Dependabot: Group your updates, slow the cadence, keep security fast
Dependabot keeps your dependencies current, but its defaults can flood your repository with pull requests. Here’s how grouping updates, slowing the cadence, and keeping security fixes fast cut the noise on a Microsoft open source project.
The cost of saying yes has changed
The cost of writing code dropped; the cost of owning it didn’t. A framework for deciding which changes are actually cheap in the AI era.
Facts Only
* A single feature implementation often results in a large pull request containing multiple distinct components (data model, API route, client wiring, UI states).
* Large pull requests make review difficult and slow down the merge process.
* Stacked pull requests use decomposition to break features into logical, ordered layers.
* The proposed stack structure for adding product search involved four layers: L1 (catalog-data), L2 (search-api), L3 (chat-grounding), and L4 (grounded-ui).
* Each layer is built upon the preceding layer, establishing dependency chains (e.g., L2 depends on L1).
* Coding agents are used to execute work across these layers based on the stack structure.
* The process involves using `gh init stack`, `gh stack add`, and `gh stack push` to manage branches.
* Reviewing involves navigating a stack map, reviewing bottom-up for context, and addressing feedback at specific layer levels.
* Resolving conflicts requires cascade operations like `gh stack rebase` followed by `gh stack sync`.
Executive Summary
The article describes a method for simplifying the review process of large code changes by using stacked pull requests, enabling decomposition rather than monolithic submissions. The core problem addressed is the trade-off between large, unreviewable pull requests and numerous small ones that require manual synchronization. The proposed solution involves breaking a feature into logically ordered layers, where each layer represents a single concern (e.g., data model, API route, UI wiring). This layering allows reviewers to focus on small, contextually rich changes sequentially.
The process is illustrated by adding product search functionality to a shopping assistant, which typically results in one large pull request containing disparate elements like data models, API routes, and UI states. Stacked pull requests introduce a structured decomposition: Layer 1 establishes the foundation (data), followed by dependent layers for the API, chat grounding, and finally the user interface. This structure defines clear dependency chains, allowing specialized reviewers to focus on specific concerns independently.
The workflow involves using coding agents to generate these layered components sequentially based on the established stack structure. After generating local branches for each layer, the process utilizes GitHub's native support via the `gh stack` CLI to manage the state across pull requests and subsequent rebasing and syncing, ensuring that feedback flows predictably through the development stages before merging into the main branch.
Full Take
The narrative posits that productivity gains from AI coding agents are constrained by the existing architectural pattern of monolithic pull requests, suggesting that tools must evolve alongside development practices to maximize efficiency. The core implication is that structure—decomposition—is a necessary prerequisite for effective human oversight, regardless of automation capabilities. The mechanism demonstrated moves beyond mere code generation to establish an explicit dependency graph enforced across the Git platform via stack management.
The shift in review methodology suggests a tension between automated, agent-driven execution and contextual, human reasoning. When agents produce a giant diff, the cognitive load shifts entirely to manual verification; when they produce a stack, the cognitive load is distributed across manageable scopes. The technical mechanism for synchronization, specifically the need to force cascading rebases (`gh stack rebase`) followed by state synchronization (`gh stack sync`), highlights how deeply intertwined CI/CD and Git workflow structures are in modern development.
The system implicitly models that complexity management requires layering: foundation first, then dependent features. This mirrors principles found in system design where base layers must be stable before higher-level abstractions can be built upon them. The challenge, as suggested by the process flow, lies in reliably automating the contextual handover of review feedback across these boundaries and ensuring that automated synchronization mechanisms do not introduce fragility or require manual intervention to maintain integrity, particularly when dealing with signed commits and branch protection rules.
What constraints are placed on the agents regarding ownership? By assigning specific layers to specific agents (e.g., Data Modeler Agent for L1), the structure implies a division of responsibility that must be maintained across subsequent steps. The system demands that autonomy is achieved not by ignoring process, but by rigorously formalizing the process itself into an auditable stack structure.
What questions remain regarding this pattern? How does this stacked approach scale to systems with highly interwoven dependencies spanning dozens of layers, and what are the inherent risks when relying on agent-enforced decomposition versus explicit human architectural oversight? Does the automation of the rebase/sync process risk obscuring subtle context errors that a reviewer might otherwise catch?
Sentinel — Human
This text exhibits a high degree of structured, opinionated argumentation grounded in specific, practical experience within software development, indicating strong human authorship focused on process critique rather than pure information delivery.
