Merge Queues: Keeping Main Green Without a Human Babysitter
Branch protection catches what a reviewer notices. A merge queue catches what four simultaneous pull requests do to each other after they are already approved.
We had a rule that main must be green. We also had six engineers who merged whenever their own build passed. Both were true at the same time, which is how I spent a Thursday afternoon bisecting a failure that four individually-approved pull requests had created between them.
Branch protection answers "is this change safe?" A merge queue answers a different question: "is this change safe as it will land, alongside everything ahead of it in line?"
The failure that approved reviews cannot see
Each of the four pull requests was reviewed. Each passed CI against the tip of main when it was opened. Two refactored the same helper with different signatures, one moved a test fixture, one deleted a constant the others still imported.
None of that is a review failure. Reviewers evaluate a diff against the branch it came from. The breakage only exists in the merge order nobody saw.
PR #412 refactors client.go -> green on its own
PR #417 moves the test fixture -> green on its own
PR #419 removes a shared const -> green on its own
main after all three -> does not compile
A merge queue exists to test that fourth line before it becomes main.
What the queue actually does
Instead of merging directly, your pull request enters a line. The queue takes the head of the line, rebases it onto main, runs the full suite, and merges only if that combination passes. The next item then does the same against the newly updated main.
# GitHub merge queue, expressed as intent
merge_queue:
merges_per_batch: 2 # test a batch, not one at a time
minimum_grouping_size: 2
checks:
- build
- unit
- integration
The important word is batch. Testing strictly one pull request at a time is correct but slow, and slow queues get bypassed — which is the actual failure mode of every queue I have watched teams adopt. Grouping two or four at a time keeps the line moving while still catching interactions between neighbours.
The properties worth arguing about
Ordering. Some queues sort by size, some by age, some leave it alone. Sorting by size tests large changes early, when the line is shortest, so a conflict surfaces while context still exists.
Bisection on failure. When a batch fails, the good version of the feature is splitting it. A queue that marks the whole batch failed and makes humans work out which of the four is at fault has just moved the Thursday afternoon somewhere else.
Timeouts and escaping. The queue needs a documented emergency path — a break-glass permission that bypasses it, logged and reviewed afterwards. If bypassing is impossible, someone will delete the queue at 4pm on a bad day, and it will not come back.
What counts as a check. If only unit tests gate the queue, you have built a faster way to merge integration failures. Include the checks you would want on a release, because the queue's output is your release candidate.
Why teams quietly disable it
Three reasons, in the order I have seen them.
The queue is too slow, because every check runs and batching is off, so developers stack up and start asking for bypass. The queue is flaky, so a failure tells you nothing about which change caused it, and rerunning becomes a ritual. And the queue conflicts constantly, because rebasing every batch against a moving main surfaces conflicts that a human would have resolved once by hand.
All three are configuration problems, but they present as "merge queues don't suit our team," and the setting gets turned off permanently.
What it costs
You pay in latency — a pull request now waits for everything ahead of it — and in infrastructure, since every batch is a full CI run. You gain a property that no amount of individual review provides: main is green as a sequence, not just as a set of diffs.
That distinction is the whole point. It is also the same discipline as quarantining flaky tests: both are about whether your signal means what you have decided it means. A queue gating on a suite nobody trusts will simply automate the distrust.
Summary
Branch protection reviews changes; a merge queue reviews combinations. Test each candidate rebase onto real main, batch them so the line moves, include the checks you would want at release time, and keep a logged break-glass path so the queue never becomes the reason someone works around it. The goal is not a stricter process — it is deleting the Thursday afternoon where four approved changes failed together.
SDP Clouds Team
DevOps and cloud engineers writing practical, battle-tested guides on CI/CD, Kubernetes, infrastructure as code, and production operations — every article is based on real incidents and real pipelines, not docs-page rewrites.
More about us →