---
title: Stacked Pull Requests
description: "How the Merge Queue lands stacked pull requests: queue propagation, stack-aware batching, cascade dequeue, and partial landings."
---

The Merge Queue lands stacked pull requests: chains where each pull request
builds on the one below it. It recognizes two kinds of stack, and once they are
in the queue it treats them the same way: a comment on the top member queues
the whole chain, the members keep their order and ride in the same batch when
it has room for them, and pulling one out pulls out everything above it.

## The Two Kinds of Stack

They differ in how the chain is created and how the queue recognizes it.

### GitHub-Native Stacked Pull Requests

GitHub has its own stacking model. You reach it with
[gh-stack](/stacks/compare/gh-stack), its stacking extension for the `gh`
CLI, or with [`mergify stack push`](/stacks/creating), which registers the
stack with GitHub's stacking API unless you pass
[`--no-github-native`](/stacks/setup#configuration). Each member is a separate
branch targeting the branch below it, and GitHub itself holds the ordering, so
the pull request bodies carry no marker.

GitHub has no auto-merge for stacked pull requests, so without a queue each
member is merged by hand as the one below it lands.

Mergify must be a bypass actor with the `exempt` bypass mode on every GitHub
ruleset that applies to the base branch. With any other bypass mode, the
Merge Queue refuses the pull request. See [GitHub Rulesets
Compatibility](/merge-queue/github-rulesets#bypass-actors) for how to set it.
This requirement is specific to GitHub-native stacks.

### Mergify Stacks

[Mergify Stacks](/stacks) are created with
`mergify stack push --no-github-native`, which maps each commit on a single
branch to its own pull request. There is no stack object on GitHub's side, so
the queue recognizes the chain only when **all** of these hold at every step:

- The PRs are physically chained: each PR's base branch is the previous PR's
  head branch.

- Each PR carries a `Depends-On: #N` marker in its body, declaring its
  dependency on the previous PR.

- Every PR's head branch lives in the repository the stack targets, not in a
  fork.

PRs chained only by branch refs (for example, GitFlow promotion chains like
`dev` → `staging` → `prod`) are **not** treated as a stack. Without the
`Depends-On:` marker, the queue keeps each PR's literal base ref and queues
them independently.

The last condition is why a stack cannot be opened from a fork. The first two
signals compare branch *names*, which only mean something inside one
repository: any fork can have a branch called `main`, so a fork PR's head
branch tells the queue nothing about where this repository's branches point.
A PR opened from a fork keeps its own base ref and is queued on its own.

<GitGraph
  commits={["A", "B", "C"]}
  commitRole="queued"
  prs={[
    { label: "PR #1", commits: 0, annotation: "base: main" },
    { label: "PR #2", commits: 1, annotation: "base: PR #1" },
    { label: "PR #3", commits: 2, annotation: "base: PR #2" },
  ]}
/>

## Queueing a Whole Stack at Once

Run [`@mergifyio queue`](/commands/queue) on the **top** PR of a stack and the
queue command propagates synthetically to every predecessor. The whole stack
enters the queue from a single comment. You don't need to comment on each PR.

For a stack `PR1 → PR2 → PR3`, commenting `@mergifyio queue` on PR3 enqueues
PR1, PR2, and PR3 in the right order. While PR3 waits for its predecessors to
join the queue, its Summary check lists one pending `depends-on=` condition
per predecessor, each tagged `[stack]`. That's the queue holding PR3 back
until PR1 and PR2 are queued ahead of it.

Propagation reaches predecessors only. Commenting on PR2 enqueues PR1 and PR2 and
leaves PR3 where it is, so queue the highest member you want to land.

:::tip
  This works the same with [Auto-Merge](/merge-protections/auto-merge):
  approve the top PR with Auto-Merge enabled and the entire stack flows into
  the queue as soon as `queue_conditions` are met.
:::

## Stack-Aware Base

Every stacked PR is queued against the **stack root** (e.g. `main`), not its
immediate parent branch. Without this, PR2 would be queued against PR1's head
branch and could never reach `main`, so the queue would have nothing to merge
into.

```dot class="queue" title="Every pull request in a stack is queued against main, not against its parent branch"
digraph {
  PR1 [class="queued"];
  PR2 [class="queued"];
  PR3 [class="queued"];
  main [label="main", class="external"];

  PR1 -> main;
  PR2 -> main;
  PR3 -> main;
}
```

You don't configure this. It's automatic for any PR detected as part of a
stack.

## Stack-Aware Batching

The queue treats a stack as an ordered chain when assembling
[batches](/merge-queue/batches). Two guarantees hold:

- **Same scope group.** With [scopes](/merge-queue/scopes) enabled, stacked
  PRs are consolidated into the scope group of the bottom PR, even if their
  individual scopes differ. The stack always travels through the same CI lane
  rather than getting split across unrelated lanes.

- **Bottom-up order.** Within that group, predecessors always queue ahead of
  successors. PR3 is never validated before PR1 and PR2.

In sequential batching, the queue actively packs a stack into the same batch
when its predecessors still fit in the remaining capacity. In parallel
checks, a stack longer than `batch_size` (or a stack sharing its scope group
with higher-priority unrelated PRs) lands across consecutive batches. Order
is preserved either way.

```dot class="queue" title="Merge queue with batch_size: 5 — the whole stack lands in one batch"
digraph {
  subgraph cluster_batch1 {
    class="batch";
    label="Batch 1 — the stack kept together";
    PR1 [class="queued"];
    PR2 [class="queued"];
    PR3 [class="queued"];
    PR4 [class="queued"];
    PR1 -> PR2 -> PR3 -> PR4;
  }

  PR5 [label="…", class="queued"];
  PR4 -> PR5;
}
```

A PR only joins a batch if it and its still-waiting predecessors fit inside
the remaining capacity. When the stack is larger than `batch_size`, it lands
across consecutive batches bottom-first: the first batch validates the deepest
PRs that fit, and once they merge they drop out of the predecessor set, so the
next batch picks up where the previous one stopped.

## Cascade Dequeue

When a PR is taken out of a queued stack with
[`@mergifyio dequeue`](/commands/dequeue), from the dashboard, or through the
API, every successor still in the queue is dequeued with it, under the
[`stack-predecessor-dequeued`](/configuration/data-types#queue-dequeue-reason)
dequeue reason. This stops the queue from validating PRs whose dependency just
disappeared. There's no point checking PR3 if PR1 has left the queue.

```dot class="queue" title="Cascade dequeue: dequeuing PR1 also dequeues PR2 and PR3; PR4 stays in the queue"
digraph {
  PR1 [label="PR1\n(dequeued)", class="failed"];
  PR2 [label="PR2\n(cascaded)", class="muted"];
  PR3 [label="PR3\n(cascaded)", class="muted"];
  PR4 [class="queued"];

  PR1 -> PR2 [label="dequeue", class="failed"];
  PR2 -> PR3 [label="dequeue", class="failed"];
  PR3 -> PR4 [style=dashed, class="muted"];
}
```

Once the PR you pulled out is ready again, re-queue the stack from the top with
`@mergifyio queue`. Propagation re-enqueues the predecessors as needed.

:::note
  Cascade dequeue only affects PRs that are still **queued**. PRs that already
  merged successfully (lower in the stack) are untouched.
:::

## How a Stack Lands

Members land from the bottom up. A batch that holds the whole stack lands every
member of it, in order, and merged members stay in a GitHub-native stack, so
that stack never shrinks.

A stack can also be partly landed while the rest is still in flight: you queued
only part of it, it is longer than `batch_size`, or a member above the ones that
merged failed. Whatever the reason, the members that already merged stay merged.

### After a Partial Landing

The members still open are **not rebased** off the commits that just landed.
For a GitHub-native stack, GitHub retargets the next member onto the stack's
base branch and stops there. For Mergify Stacks, GitHub retargets the
next member when the merged head branch is deleted, which is what
[automatic head-branch deletion](/stacks/setup#3-configure-github) is for.
Either way, nothing rewrites the remaining branches.

So each remaining member still carries its predecessor's pre-landing commits.
When the queue squashes or rebases, what landed on the base branch has new
SHAs, so the member still shows that change in its diff and conflicts wherever
the two touch the same lines. A member already in the queue when its
predecessor landed can fail its merge for that reason; one queued afterwards is
taken back out of the queue for conflicting with its base branch. With `merge_method: merge` the
original commits land unchanged and the remaining members merge cleanly.

Rebase the remaining members before queueing them again:

- **Mergify Stacks**: run [`mergify stack sync`](/stacks/updating#after-a-pr-merges)
  to drop the merged commits and rebase the rest, then `mergify stack push`.

- **GitHub-native stacks**: run `gh stack sync`, or rebase each remaining
  branch on the base branch by hand.

Then comment `@mergifyio queue` on the top member again. Propagation puts the
rest of the chain back in the queue.

:::tip
  Give `batch_size` room for the stacks you usually open. When a whole stack
  fits in one batch and its checks pass, it lands in a single pass and nothing
  is left to rebase.
:::

## Limits

- **Maximum stack depth: 20.** Stacks deeper than 20 PRs aren't recognized as
  a stack by the queue and fall back to per-PR queueing.

- **A draft predecessor holds the stack.** Propagation still reaches it: the
  draft PR gets its own queue command, then waits on the same `-draft`
  condition as every queued PR. Nothing above it is validated until you mark it
  ready for review, at which point it joins the queue. You don't have to
  comment `@mergifyio queue` again.

## Related

- [Stacks](/stacks): create and update stacks with `mergify stack push`.

- [`@mergifyio queue`](/commands/queue): the command that triggers stack
  propagation.

- [Batches](/merge-queue/batches): batch-size and CI-cost trade-offs.

- [Scopes](/merge-queue/scopes): how stacks interact with monorepo scopes.

- [GitHub Rulesets Compatibility](/merge-queue/github-rulesets): the
  `exempt` bypass mode GitHub-native stacks require.
