Stacked Pull Requests
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
Section titled The Two Kinds of StackThey differ in how the chain is created and how the queue recognizes it.
GitHub-Native Stacked Pull Requests
Section titled GitHub-Native Stacked Pull RequestsGitHub has its own stacking model. You reach it with
gh-stack, its stacking extension for the gh
CLI, or with mergify stack push, which registers the
stack with GitHub’s stacking API unless you pass
--no-github-native. 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 for how to set it.
This requirement is specific to GitHub-native stacks.
Mergify Stacks
Section titled Mergify StacksMergify 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: #Nmarker 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.
Queueing a Whole Stack at Once
Section titled Queueing a Whole Stack at OnceRun @mergifyio 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.
Stack-Aware Base
Section titled Stack-Aware BaseEvery 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.
You don’t configure this. It’s automatic for any PR detected as part of a stack.
Stack-Aware Batching
Section titled Stack-Aware BatchingThe queue treats a stack as an ordered chain when assembling batches. Two guarantees hold:
-
Same scope group. With 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.
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
Section titled Cascade DequeueWhen a PR is taken out of a queued stack with
@mergifyio dequeue, from the dashboard, or through the
API, every successor still in the queue is dequeued with it, under the
stack-predecessor-dequeued
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.
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.
How a Stack Lands
Section titled How a Stack LandsMembers 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
Section titled After a Partial LandingThe 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 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 syncto drop the merged commits and rebase the rest, thenmergify 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.
Limits
Section titled 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
-draftcondition 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 queueagain.
Related
Section titled Related-
Stacks: create and update stacks with
mergify stack push. -
@mergifyio queue: the command that triggers stack propagation. -
Batches: batch-size and CI-cost trade-offs.
-
Scopes: how stacks interact with monorepo scopes.
-
GitHub Rulesets Compatibility: the
exemptbypass mode GitHub-native stacks require.
Was this page helpful?
Thanks for your feedback!