Parallel Scopes
Run independent pull requests through the merge queue simultaneously to merge faster in monorepos.
By default, Mergify’s merge queue operates in serial mode: every pull request is tested on top of the previous one, forming a single ordered pipeline. This guarantees correctness but means unrelated changes wait for each other.
Parallel mode removes that constraint. When two pull requests touch different areas of the codebase — different scopes — Mergify tests and merges them independently, at the same time. Pull requests that do share a scope are still queued together so they are tested as a group, preventing semantic conflicts.
Serial vs. Parallel at a Glance
Section titled Serial vs. Parallel at a GlanceIn serial mode, every batch depends on the one before it. Even if PR #3 (docs) has nothing in common with PR #1 (api) or PR #2 (frontend), it still waits:
In parallel mode, Mergify groups pull requests by scope. Batches that share no scope run at the same time:
When scopes do overlap, Mergify preserves ordering within that scope to guarantee the changes are tested together:
Here PR #4 touches the api scope, just like PR #1 — so it must wait for PR #1 to merge first.
Meanwhile PR #3 (docs) proceeds independently.
Setting Up Parallel Mode
Section titled Setting Up Parallel ModeParallel mode requires two things: switching the queue mode and configuring scopes so Mergify knows which areas of the codebase each pull request touches.
1. Define scopes
Section titled 1. Define scopesScopes can come from file patterns declared directly in .mergify.yml, or from an external build
system (Nx, Bazel, Turborepo, …) via the
gha-mergify-ci GitHub Action.
scopes: source: files: api: include: - services/api/**/* frontend: include: - apps/web/**/* docs: include: - docs/**/*See Scopes for all configuration options and build-tool integrations.
2. Enable parallel mode
Section titled 2. Enable parallel modeAdd mode: parallel under merge_queue:
merge_queue: mode: parallel max_parallel_checks: 5
scopes: source: files: api: include: - services/api/**/* frontend: include: - apps/web/**/* docs: include: - docs/**/*
queue_rules: - name: default batch_size: 3 queue_conditions: - check-success = ciThe max_parallel_checks setting controls how many batches Mergify tests at the same time across
all scope queues. Tune it to match your CI capacity.
How It Works
Section titled How It WorksOnce parallel mode is active, the merge queue follows these steps whenever it processes pull requests:
-
Scope assignment. Each pull request is tagged with the scopes it affects, either automatically from file patterns or via an external upload.
-
Batch formation. Mergify groups pull requests that share exactly the same set of scopes into batches (respecting
batch_size). Pull requests with different scopes form separate batches. -
Dependency tracking. Batches that share at least one scope are linked as parent → child in a dependency graph. A child batch cannot merge until all its parents have merged.
-
Parallel execution. Batches with no shared scopes — and therefore no dependency — are tested by CI at the same time, up to
max_parallel_checks. -
Merge. As soon as a batch’s CI passes and all its parent batches are merged, Mergify merges the pull requests in that batch.
What happens when a batch fails?
Section titled What happens when a batch fails?The failure handling works the same way as in serial mode: Mergify splits the failed batch and retests the parts to isolate the problematic pull request. See Handling Batch Failures for details.
Because batches in parallel mode are scoped, a failure in one scope queue does not block unrelated scope queues. Only batches that depend on the failed one (via shared scopes) are affected.
Limiting Concurrency per Scope
Section titled Limiting Concurrency per Scopemax_parallel_checks caps how many speculative checks run at once across all scopes. Sometimes
you want to bound a single scope on top of that: a scope whose tests are expensive or hit a shared
resource that can’t take many concurrent runs (a staging environment or a rate-limited external
service), while the rest of your scopes can use whatever capacity the global ceiling leaves them.
scopes.capacities maps a scope name to the number of speculative checks that scope may run at the
same time:
merge_queue: mode: parallel max_parallel_checks: 5
scopes: source: files: frontend: include: - apps/web/**/* backend: include: - services/api/**/* docs: include: - docs/**/* capacities: frontend: 2 backend: 2Here frontend and backend are each limited to 2 concurrent speculative checks. docs is absent
from the map, so it stays uncapped: only the global ceiling applies to it.
How capacities relate to the global ceiling
Section titled How capacities relate to the global ceilingmax_parallel_checks is the global ceiling: the most speculative checks a train will ever run at
once. Each scopes.capacities entry is a sub-limit inside that ceiling, not an extra budget on
top of it:
-
A speculative check consumes one global slot and one slot in every capped scope its batch belongs to.
-
It starts only when the global ceiling has room and each of its capped scopes has room.
-
A scope that isn’t listed in
capacitiesis unlimited; it draws on the global ceiling alone.
Because every check always takes a global slot, the total running at once never exceeds
max_parallel_checks, whatever you put in capacities. Capacities can only ever hold a scope
below the global ceiling; they never raise the total, so adding them to an existing configuration
cannot increase your CI load.
Worked example
Section titled Worked exampleTake the configuration above (max_parallel_checks: 5, frontend: 2, backend: 2, docs
uncapped) and suppose the queue is ready to test three frontend batches, three backend batches,
and two docs batches. The slots might fill like this:
-
frontendandbackendeach run at most 2 batches, so each holds back its third; those wait for a free slot in their own scope. -
docsis uncapped, but only as manydocsbatches run as there is room under the ceiling of 5. Here that is 1, so the seconddocsbatch waits, not becausedocsis capped (it isn’t) but because the global ceiling is full.
Two things always hold: no capped scope runs more than its limit, and no more than
max_parallel_checks run at once. Exactly which batches fill the slots, and whether the last free
slot goes to docs or to a capped scope still below its limit, follows queue order, so the split
can differ from one cycle to the next. As soon as a running check finishes, its freed global slot
(and its freed scope slot, if any) go to the next waiting batch that fits both.
Pull requests in several scopes
Section titled Pull requests in several scopesA batch that touches more than one capped scope must fit in all of them at once. A batch
carrying both frontend and backend consumes one frontend slot and one backend slot, and
starts only when both scopes, and the global ceiling, have room. This keeps every scope’s limit
honored even when changes span scopes.
Source-agnostic
Section titled Source-agnosticcapacities only sets the limit; it does not decide which pull requests belong to a scope.
Membership comes from your scopes.source, so capacities behave the same
whether scopes are derived from file patterns (source: files)
or pushed from an external build system (source: manual). You declare the limit once, no matter how
membership is computed.
The Monorepo Trade-Off
Section titled The Monorepo Trade-OffParallel mode is built for the reality of monorepos: most pull requests are independent, but some do interact.
| Scenario | What happens | Benefit |
|---|---|---|
| PRs touch different scopes | Tested and merged in parallel | Faster merge times — no waiting for unrelated work |
| PRs touch the same scope | Ordered within that scope queue and tested together | Conflicts caught before merge |
| A PR touches multiple scopes | Linked to all relevant scope queues | Correctness preserved across scopes |
The net effect: pull requests merge faster when their scopes don’t collide, while pull requests that do collide are still tested in the right order to avoid semantic conflicts reaching your main branch.
Compatibility and Limitations
Section titled Compatibility and LimitationsParallel mode changes how the queue operates. Some features that rely on strict single-queue ordering are not available:
-
Scopes are required. You must configure
scopes.source(eitherfilesormanual). Without scopes, Mergify cannot determine which pull requests are independent. -
fast-forwardmerge is not supported. Because batches merge independently, Mergify needs to rebase them. Usemergeorrebaseas yourmerge_method. -
skip_intermediate_resultsis not available. This feature depends on the strict cumulative ordering of serial mode.
Next Steps
Section titled Next Steps-
Scopes: learn how to define and manage scopes for your repository.
-
Batches: understand batch formation, sizing, and failure handling.
-
Performance: tune your queue for the right balance of speed, cost, and reliability.
-
Monorepo: broader guidance on using Mergify in monorepo setups.
Was this page helpful?
Thanks for your feedback!