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.
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. The bottom PR has no previous PR and targets the destination branch directly.
-
Each PR above the bottom one carries a
Depends-On: #Nmarker in its body, declaring its dependency on the previous PR. The bottom PR depends on nothing, so it carries no marker. -
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 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 queues
PR1, PR2, and PR3 together: none of them enters the queue until all three meet
their queue_conditions. They
then enter in order, PR1 first, so PR1 and PR2 can’t enter the queue, and merge,
while PR3, the one you asked to land, doesn’t meet its queue_conditions yet.
While the stack waits, the status comment
and the Mergify Merge Queue check
run of each ready
member read Waiting for the rest of the stack and list the members whose
queue_conditions aren’t met yet. A member that isn’t ready lists its own unmet
conditions, as any pull request does.
Propagation reaches predecessors only. Commenting on PR2 queues PR1 and PR2 together and leaves PR3 where it is, so queue the highest member you want to land. If you queue two members of the same stack, the stack waits for every member up to the higher one.
Queueing with the checkbox
Section titled Queueing with the checkboxThe Queue this pull request checkbox
shows on a stacked PR as soon as its own queue_conditions are met, even while
the members below it aren’t queued yet. Ticking it on the top PR queues the
whole stack, exactly like commenting @mergifyio queue there.
When the queue command is cancelled
Section titled When the queue command is cancelledThe members below follow the queue command on the top PR. That command is
cancelled when new commits are pushed to PR3, when PR3 is dequeued with
@mergifyio dequeue, or when PR3 is closed. If that
happens before the stack enters the queue, the members below drop the command
too, and their status comment names PR3. Members that are already in the queue
stay there. Queue PR3 again, reopening it first if it was closed, to queue the
stack.
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 queues the predecessors again 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. The command depends on the tool that pushed the stack:
-
Pushed with
mergify stack push, with or without--no-github-native: runmergify stack syncto drop the merged commits and rebase the rest, thenmergify stack pushagain. -
Created with another stacking tool: use that tool’s own sync command (
gh stack syncfor gh-stack), 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. Like any member whosequeue_conditionsaren’t met, it keeps the stack out of the queue until you mark it ready for review. The stack then enters the queue together once every other member is ready too. 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!