Monorepo merge queue
A monorepo puts every team on one main branch, which is exactly where a merge queue earns its keep. This guide is about the monorepo-specific parts: running an independent lane per project, batching within a scope, and bisecting failures across packages. If you want the general mechanics first, start with what a merge queue is.
In one paragraph
In a monorepo, every team merges into the same main branch, so a single merge queue would serialize the whole company behind one lane. A scope-aware queue splits that into an independent lane per project: unrelated PRs merge in parallel, PRs that cross scopes join every lane they touch, and batching plus bisection keep the CI bill down without stalling good changes. Scopes come from the same project boundaries your monorepo CI already uses.
Why one main branch becomes the bottleneck
The whole point of a monorepo is that everything lands on one branch. That is also what turns merging into a queue problem. Every team, every service, every package competes for the same main, and the changes are developed in parallel against snapshots of main that are already going stale.
The result is the same race a merge queue solves anywhere, amplified by contributor count. A function gets renamed in one project while another project adds a caller. Git reports no conflict, each PR passed CI on its own, and main breaks the moment both land. In a small repo this is occasional. In a monorepo with dozens of teams it is a daily event, and the naive fix of serializing every merge into one queue trades a broken main for a slow one.
Scope lanes: a queue per project
flowchart LR Q["queued PRs"] Q --> FE["frontend lane<br/>#101 → #104"] Q --> BE["backend lane<br/>#102"] Q --> XC["cross-cutting<br/>#103 (both lanes)"] FE --> Main["main stays green"] BE --> Main XC --> Main style Main fill:#E6F8F2,stroke:#1CB893,color:#1A1D24
Independent lanes per scope run in parallel. A PR that touches two scopes joins both lanes and merges only after passing each.
A scope-aware merge queue reads the project boundaries in your repository and runs a separate lane for each one. A PR that only touches the frontend lives in the frontend lane and never waits on a backend migration in another. Lanes advance independently, so the throughput of the whole queue grows with the number of scopes instead of being capped at one merge at a time.
Cross-cutting PRs are the interesting case. A change that touches two scopes joins both lanes and merges only once it has passed against the future state of each. That keeps the correctness guarantee intact: main is still tested as it will actually look, even when a change spans projects. Mergify implements this with scopes declared once in .mergify.yml. For the mechanics of parallel lanes in general, see parallel queues.
Batching and bisection across packages
A monorepo CI run is expensive, so testing every queued PR on its own is the wrong default. Batching groups several PRs from the same lane into one CI run. Instead of ten runs for ten frontend PRs, the queue does a small number of batched runs and merges the whole group when they pass.
The catch with batching is failure handling, and this is where a monorepo needs bisection. When a batch fails, the queue splits it and re-tests the halves to find the PR that broke it. The culprit is removed and its author notified, and the innocent PRs in the batch still merge. You get the cheaper CI bill of batching without one bad change dragging down every PR that happened to be batched with it. See batching for the full walkthrough.
One set of scopes, shared with CI
The queue does not need its own idea of the repository's structure. It reuses the project boundaries that monorepo CI already relies on to decide which tests a PR should run. The same scopes that tell CI "this PR touches the frontend, run the frontend suite" tell the queue "this PR belongs in the frontend lane."
Declaring scopes once and reading them from both places is what keeps the two systems consistent. A PR cannot be tested as a frontend change and then queued as something else, and adding a new project updates both the CI routing and the queue lanes at the same time.
FAQ
Why does a monorepo need a merge queue?
A monorepo puts every team on one main branch, so every merge competes with every other merge. Two PRs that each passed CI against an older main can break main when they land together, and the more contributors share the branch the more often that happens. A merge queue tests each PR against the state main will actually have at merge time, so main stays green no matter how many people ship at once.
What is a scope-aware merge queue?
A merge queue that understands the project boundaries in a monorepo and runs an independent lane per scope. A frontend PR and a backend PR that touch different projects merge in parallel instead of serializing. A PR that touches both scopes joins both lanes and merges only after passing each. Without scopes, a single queue serializes every merge in the repo, which becomes the bottleneck a monorepo was supposed to avoid.
How does batching work in a monorepo merge queue?
Batching groups several queued PRs into one CI run instead of testing each individually. In a monorepo the batches are scope-aware, so only PRs in the same lane are grouped and an unrelated project never contaminates the batch. If the batch fails, the queue bisects to find the culprit and lets the innocent PRs through, which keeps the CI bill down without stalling good changes.
What happens when tests fail across packages?
When a batched CI run fails, the queue bisects the batch to isolate the PR that caused it. The failing PR is removed and its author is notified, while the rest of the batch is re-tested and allowed to merge. Because lanes are scoped, a failure in one project does not block merges in unrelated projects.
Is this different from GitHub's native merge queue?
GitHub's native queue serializes merges and does not run parallel lanes per scope, batch with bisection, or offer queue analytics. For a single-team repo that can be enough. For a monorepo where several teams share one main branch, the lack of scoping means every merge waits behind every other. See the merge queue guide for the full comparison.
How does the merge queue relate to monorepo CI?
They reuse the same project boundaries. Affected-projects detection decides which tests to run on a PR, and the merge queue reuses those scopes to decide which lane a PR belongs to and how to batch it. Set the scopes once and both the CI and the queue read from them.
A merge queue built for monorepos.
Mergify runs independent merge lanes per scope, batches within a scope, and bisects failures so one bad PR never blocks the rest. Built for monorepos where every team shares one main branch.