> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stacked Pull Requests

> Split large work into a stack of dependent pull requests so each one stays reviewable, and merge the stack bottom-up without losing the chain.

<Info>
  **Prerequisite**: You should be comfortable with [Git
  Conventions](/build/development-tools/git-conventions) and with creating
  worktrees, since every PR in a stack gets its own.
</Info>

## TL;DR

A stack is a chain of pull requests where each one targets the branch below it
instead of `main`. Use it when a change is too large to review in one sitting,
or when follow-up work builds on a branch that has not merged yet.

GitHub has a first-class feature for this, currently **in public preview**. It
tracks the chain as a unit, and merging one pull request rebases and retargets
the rest. Use it when you can:

```bash theme={null}
gh extension install github/gh-stack
```

Everything still merges bottom-up — that is GitHub's rule, not a convention:
"pull requests must merge from the bottom up."

<Warning>
  Merging the **top** pull request merges the whole stack: "Every pull request
  below it comes with it." If you only mean to land the bottom one, merge the
  bottom one.
</Warning>

## Native stacks vs. base chaining

Two things get called "stacked PRs", and they behave differently on merge.

**Native stacks** — created with `gh stack` or the web UI. GitHub knows the
chain exists. Merging a mid-stack pull request merges everything below it, and
"the pull requests above stay open and automatically re-target the stack's base
branch." All three merge methods work: "Stacks support merge commit, squash,
and rebase merge methods, and they are merge-queue aware."

**Base chaining** — plain `gh pr create --base <parent-branch>`, with no stack
object behind it. This is what you get by default, and what the rest of this
page's manual steps describe. It mostly works, but the merge-time guarantees
above do **not** apply to it; see [Base chaining without a native
stack](#base-chaining-without-a-native-stack).

Limits on the native feature, both from GitHub: it "is in public preview and
subject to change", and it "require\[s] all branches to be in the same
repository. Cross-fork stacks are not supported." It is also "not supported in
GitHub Desktop", so a contributor working from a fork is on the base-chaining
path whether they want to be or not.

## When to stack

Stack when the parts are **genuinely dependent**:

* A large feature that splits into reviewable slices, where slice 2 imports
  code slice 1 introduces.
* Follow-up work on a branch that is still waiting for review — stacking lets
  you keep building instead of idling or piling more into a PR someone has
  already started reviewing.
* A refactor that must land before the change that depends on it, where you
  want them reviewed separately but merged together.

**Do not stack when the parts are independent.** Two unrelated fixes should be
two PRs off `main`: they can merge in any order, neither blocks the other, and
neither needs a rebase when the other lands. A stack buys ordering at the cost
of rebasing, so only pay it when you need the ordering.

## Creating a stack

Each PR gets its own worktree, per the repo-wide rule that PR work never
switches the shared checkout:

```bash theme={null}
git worktree add -b feat/slice-one .worktrees/slice-one origin/main
git worktree add -b feat/slice-two .worktrees/slice-two feat/slice-one
```

Then open each PR against the branch below it. The first targets `main`; every
later one targets its parent:

```bash theme={null}
gh pr create --base main --head feat/slice-one
gh pr create --base feat/slice-one --head feat/slice-two
```

Basing the child on the parent is what keeps the diff honest: GitHub diffs
against the merge base, so the child PR shows only its own commits rather than
re-displaying everything in the parent.

### Say where each PR sits

Reviewers cannot see the stack from the PR page alone. Put the position and the
parent in the body of every PR above the base:

```markdown theme={null}
**Stack**: 2 of 3 — based on #5145, which must merge first.
```

## Keeping each PR green

Every PR in the stack must pass on its own. A child that only compiles once its
parent is merged is not reviewable, and CI will say so.

Run the usual gates in the child's worktree, not just at the top of the stack:

```bash theme={null}
bun check
```

`bun check` does not compile Next apps, so a slice that touches routes, pages
or dependencies also needs the affected app's real build before it can be
called verified.

<Warning>
  Agents must not run `bun run build` on their own — the repo-wide prohibition
  on long-running build commands applies inside a stack too. Report that the
  build is required and ask; run it only once the user says so.
</Warning>

## Merging the stack

Merge **bottom-up**, always. GitHub states it as a requirement, not a
preference: "pull requests must merge from the bottom up." Never merge a pull
request whose parent is still open and unmerged — that pulls the parent's
unreviewed commits into `main` through the child.

How much lands depends on which one you merge:

| You merge      | What lands                     | What stays open              |
| -------------- | ------------------------------ | ---------------------------- |
| The bottom PR  | Just it                        | Everything above, retargeted |
| A mid-stack PR | It **and everything below it** | Everything above, retargeted |
| The top PR     | The **whole stack**            | Nothing                      |

<Warning>
  "Merge the entire stack at once by merging the top pull request. Every pull
  request below it comes with it." If you meant to land one slice and clicked
  merge on the top of the stack, you have merged all of it.
</Warning>

On a native stack that is the whole procedure. The merge method is free —
"Stacks support merge commit, squash, and rebase merge methods, and they are
merge-queue aware" — and the branches above are rebased and retargeted for you.

### Base chaining without a native stack

A chain built with `gh pr create --base` has no stack object behind it, so none
of the guarantees above apply. Two extra rules:

**Merge the parent with a merge commit.**

```bash theme={null}
gh pr merge <parent-number> --merge
```

Not `--squash` and not `--rebase`. Both rewrite the parent's commits into new
ones, so the originals never enter `main`'s ancestry — and the child, once
retargeted, shows the parent's changes all over again as if they had never
landed. A merge commit keeps them reachable from `main`, which is what makes
the child's diff shrink to its own work. If a parent has already been
squash-merged, rebase the child onto `main`, drop the duplicated commits, and
re-run its gates.

**Check the retargeting actually happened.** When a merged parent's branch is
deleted through the pull-request flow — the merge button, or the repository's
automatic head-branch deletion — GitHub retargets any open PR that had it as a
base onto the parent's base.

<Warning>
  Do not assume it did. Deleting the branch another way — `gh` or
  `git push origin --delete` — can **close** the dependent pull requests
  instead of retargeting them, and a closed PR whose base branch is gone is
  awkward to reopen. Closing a parent without merging does not delete its
  branch at all, so its children keep pointing at a live but abandoned base.
</Warning>

```bash theme={null}
gh pr view <child-number> --json baseRefName -q .baseRefName
gh pr edit <child-number> --base main
```

Then re-run the child's gates: its merge base moved, so its diff is no longer
the one that was last verified.

### Either way

Each pull request needs its own approving review from a code owner and its own
resolved review threads — the repository ruleset applies per PR, not per stack.
Follow the normal merge closeout (quiet window, main-green verification,
`bun git-sync`, production verification) for each one that lands.

## Rebasing a stack

If you rebase or amend a parent branch, every branch above it still points at
the old commits and must be rebased too, from the bottom up:

```bash theme={null}
git -C .worktrees/slice-one rebase origin/main
git -C .worktrees/slice-two rebase --onto feat/slice-one <old-parent-sha> feat/slice-two
```

Both then need a force-push. Use `--force-with-lease` so a push is refused if
someone else moved the branch:

```bash theme={null}
git push --force-with-lease origin feat/slice-one
```

<Warning>
  Force-pushing rewrites published history. It is routine for a branch only you
  are working on, but confirm with the owner before rewriting a branch anyone
  else has pulled or reviewed.
</Warning>

Rebasing a whole stack is the main cost of stacking, and it grows with depth.
Three PRs is comfortable; much beyond that, prefer merging the lower slices
sooner so the stack stays shallow.

## Cleaning up

Remove a worktree and delete its local branch only after that PR is confirmed
merged into `main`. Never remove a worktree that is dirty, blocked, unmerged, or
owned by someone else.

```bash theme={null}
git worktree remove .worktrees/slice-one
git branch -d feat/slice-one
```
