When to Outgrow GitHub's Merge Queue
GitHub's native merge queue is the right starting point. Six signals it is time to upgrade, and what each one tells you about what to switch to.
GitHub’s native merge queue is the right starting point for most teams. It costs nothing, it ships with the platform, and it solves the basic “do not break main” problem.
It also has a ceiling.
The teams we talk to who graduate from it usually report the same failure modes. PRs piling up faster than the queue can churn them. Hotfixes stuck behind feature work. The same flaky test failing four times in a row. CI bills creeping up because every PR runs a full test suite, even when ten of those PRs touch the same file.
If you are hitting any of the signals below, you have outgrown what GitHub’s queue is designed to do. None of these are bugs in GitHub’s product. They are deliberate design choices, tuned for teams that are not yet at your scale.
Signal 1: Your queue can’t keep up with your PR volume
GitHub’s queue merges PRs one at a time. Each PR runs against a fresh base built from main plus the PRs ahead of it. A serial queue can only merge as fast as CI runs: with a 30-minute suite that is about two PRs an hour. That is fine at 10 PRs a day. Once your team is merging 20 or 30 a day, the queue cannot finish one iteration before the next wave of PRs arrives, and the backlog grows faster than it drains.
flowchart LR
subgraph GH["GitHub: one CI run per PR"]
direction LR
A1["PR 1"] --> A2["PR 2"] --> A3["PR 3"] --> A4["PR 4"]
end
subgraph MG["Mergify: one CI run per batch"]
direction LR
B["PR 1 + 2 + 3 + 4"] --> R{"Batch passes?"}
R -->|"yes"| M["Merge all 4"]
R -->|"no"| S["Bisect, merge the good ones"]
end
Batching. A batched queue tests N PRs as a single unit. If the batch passes, all N merge in one go. CI runs once instead of N times, the queue clears faster, and the bill drops.
The hard part of batching is what happens when the batch fails. Naive batching reruns the whole batch on every failure, which can be worse than no batching. Smart batching splits the batch on failure and bisects to find which PR is responsible. The non-failing PRs merge, the broken one gets kicked back to its author with a real error.
Mergify’s merge queue ships batching with bisect-on-failure by default. The first month most teams see a 60-80% reduction in queue-related CI minutes for their highest-traffic repos.
Signal 2: Hotfixes sit behind feature work
GitHub’s queue treats all PRs as equal. You can use a “jump to front” mechanism to bump one PR ahead, but you cannot set multiple priority lanes or have the queue automatically route PRs to lanes based on rules.
priority:hotfixskips the queue, merges immediately
priority:defaultruns normally, in order
priority:lowwaits in line behind everything
This sounds cosmetic until you have an active incident and discover your one-shot fix is queued behind 30 feature PRs. The wait time for that fix becomes a measurable production cost.
Signal 3: You cannot afford to run full CI on every PR
A standard CI pipeline runs the same set of jobs on every push to every PR. As your test suite grows past 30 minutes, this becomes expensive. Past an hour, it becomes unworkable.
flowchart TB
P["Every push"] --> L["Light checks<br/>lint · types · changed-file unit tests"]
L --> Q{"PR enters queue"}
Q --> H["Heavy checks<br/>integration · e2e · full matrix"]
H --> MG["Merge — blessed on the in-queue result"]
Two-step CI. Light checks on every push (lint, type-check, unit tests for the changed files) and heavy checks (integration tests, end-to-end suites, full matrix runs) only when the PR enters the queue.
GitHub’s queue does not support this. You can fake it by carefully gating your workflows with if: conditions, but you lose the test-on-final-state guarantee that is the entire point of a merge queue.
Mergify’s two-step CI runs explicit pre-queue and in-queue checks. You define what runs where in YAML. The merge queue only blesses the result of the in-queue checks.
Signal 4: Your monorepo runs the wrong tests
If you have a monorepo with five services and a PR touches one service, GitHub’s queue cannot scope the test run to that service. It runs the full pipeline. At monorepo scale, this is a per-PR tax on velocity.
Scope-aware batching. The queue groups PRs by which scope (service, package, directory) they touch and tests each scope independently. A PR touching service-a/ runs service-a tests. A batch of three PRs all touching service-b/ runs service-b tests once and merges all three.
service-a/runs service-a testsservice-b/runs service-b testsservice-c/runs service-c testsEach scope is an independent lane. A PR is tested only against the scope it touches; PRs sharing a scope batch into one run.
Most engineering orgs that move to monorepos hit this wall within six months. Mergify ships scope-aware batching as part of the queue configuration. You define scopes in your config and the queue does the routing.
Signal 5: Flaky tests are quietly blocking merges
We cover this in depth in our guide to flaky tests. The short version: a merge queue without flaky test handling gets slower every month as your test suite grows.
GitHub’s queue has no concept of flaky tests. A flaky failure looks identical to a real failure. The queue blocks, the engineer reruns, the queue blocks again. You either lower your bar (auto-retry-once-then-ignore, which hides real bugs) or you suffer.
flag tests that fail then pass on the same SHA
stop them from blocking the merge
retry only the flaky one, not the suite
Signal 6: There was a silent merged-without-CI incident
GitHub’s queue rebuilds merge commits server-side after CI passes. The merged SHA is not always the SHA your CI signed off on, because the queue can re-resolve the merge against newer base commits. In rare cases, this means code lands that was not exactly the code that was tested.
flowchart TB
subgraph GH["GitHub native"]
direction TB
g1["CI passes on SHA abc123"] --> g2["Queue rebuilds the merge commit"]
g2 --> g3["Merged SHA = def456"]
g3 --> g4{"tested ≠merged"}
end
subgraph MG["Mergify"]
direction TB
m1["CI passes on SHA abc123"] --> m2["No rebuild after CI"]
m2 --> m3["Merged SHA = abc123"]
m3 --> m4{"tested = merged"}
end
This is documented and was discussed at length on Hacker News. For most teams it never matters. For teams shipping payment systems, healthcare data, or anything regulated, it is a non-starter.
Mergify does not rebuild the merge commit after CI. The tested SHA is the merged SHA. If that property matters to your audit posture, that alone is reason to switch.
What the migration looks like
For a single repo, migration is a day’s work:
Install the Mergify GitHub App on the repo
Add a .mergify.yml with your queue rules — most teams start with a simple “queue any PR with N approvals” rule and refine from there
Turn off GitHub’s queue on that repo
Watch the first 20 PRs go through
Most teams do this on a low-traffic repo first, run both queues side by side for two weeks, then migrate the rest once they have data. The comparison page has more on the feature gap and a few customer stories from teams that made the switch.
The honest framing
GitHub’s merge queue is good. It is also the floor, not the ceiling. If you are hitting the limits above and asking yourself whether you have outgrown it, you probably have. The cost of switching is one engineer-day per repo. The cost of not switching is a queue that gets slower every quarter and a CI bill that keeps climbing.
The signal we hear most often from teams that switched: “I wish we had done this six months earlier.”