DEV Community

Cover image for GitHub Merge Queue, Explained: Why Green PRs Break Main
Othman Shareef for Pyor

Posted on Originally published at pyor.review on

GitHub Merge Queue, Explained: Why Green PRs Break Main

Two pull requests can both be green and still break main the moment they both land. Each one was tested against the main that existed when its CI last ran, not against each other. The GitHub merge queue exists for exactly this failure: it revalidates every pull request against the latest version of the target branch, plus the queued changes ahead of it, before anything merges. This piece explains how the queue works, verified against the GitHub docs, when the extra machinery pays for itself, and what it pointedly does not fix.

The short answer: A merge queue tests each pull request in combination with the latest target branch and the pull requests queued ahead of it, using temporary branches, and merges only what passes. Failing entries are removed and the queue rebuilds without them. You need one when merge volume and CI duration make results stale before merging. It guarantees an integrated, green main. It says nothing about whether anyone reviewed the code well.

Green plus green equals broken

The failure is not a textual merge conflict; Git catches those. It is the semantic conflict: one PR renames a helper while another adds a call to the old name, one tightens a validation while another starts sending the newly invalid input. Both pass CI, because both were tested against a main that contained neither. The traditional fix is to update your branch and rerun checks before merging, and on a quiet repository that works fine. On a busy one it becomes a treadmill: by the time your rerun finishes, someone else merged, your result is stale again, and the fastest clicker wins. The docs name this dynamic directly: without a queue, authors must update their branch and wait for status checks to finish before trying to merge.

How the GitHub merge queue works

Per the GitHub docs on merge queues, the flow runs like this. Once a pull request passes its required checks, anyone with write access can add it to the queue. The queue then verifies that the changes pass all required status checks when applied to the latest version of the target branch and to any pull requests already queued ahead. It does this by creating temporary branches with a special prefix, each containing the target branch plus the queued changes in order, so a group of pending pull requests is effectively tested as the future main it would produce. Merges then happen in queue order, and main only ever receives combinations that passed together. Two setup details decide whether any of this functions: the branch protection rule must enable Require merge queue, and your CI has to run on the merge_group event, because without that trigger the queued validation runs no checks at all.

Failures, ejection, and jumping the queue

When a queued pull request fails checks, the docs are unambiguous: it is removed from the queue, and the temporary branches are recreated without it so the entries behind it can proceed against a clean base. The ejected author fixes and re-queues; nobody else is blocked. This is the property that makes batching safe, since one bad change cannot poison the merges around it for longer than a rebuild. There is also an escape hatch: you can move a pull request to the top of the queue. Use it the way the docs imply you should, sparingly, because a jump causes a full rebuild of every in-progress entry, which means one impatient hotfix can throw away the CI time of the entire queue behind it. A queue that gets jumped daily is a signal that either CI is too slow or too much is being treated as urgent.

When you actually need one

The docs scope the feature honestly: it is particularly useful on branches where a relatively high number of pull requests merge each day from many different users. Two variables govern the decision. Merge volume, because each merge is a chance to invalidate everyone else’s CI results, and CI duration, because slow pipelines widen the staleness window. Ten merges a day against a five-minute pipeline is annoying but survivable by hand; ten merges against a forty-minute pipeline is four hundred minutes of potential staleness, and the treadmill wins. Below that threshold, a merge queue is process for the sake of process. Above it, the queue converts a coordination problem that scaled with team size into infrastructure, which is exactly what infrastructure is for.

What a merge queue does not fix

A merge queue guarantees the combination compiles and the tests pass. It has no opinion on whether the tests assert anything, whether the design is sound, or whether a human ever read the diff; a rubber-stamped approval rides the queue exactly as smoothly as a careful one. If your main is green but your defect rate is not moving, the bottleneck is the review itself, and the evidence on what reviews actually catch says that depends on humans reading with attention, which no merge automation supplies. Disclosure: this is the layer we work on at Pyor, on the view that the useful job for tooling there is organizing the diff so the human read gets faster, not summarizing it away. Queue for integration, review for correctness; neither covers for the other.

Merge queues and trunk-based flow

Merge queues and trunk-based development solve the same problem at different layers. Trunk-based flow asks for small, frequent merges to a single branch, which is precisely the traffic pattern that makes stale CI results common, so teams that adopt the flow tend to hit the queue-shaped problem sooner. The queue, in turn, removes the biggest tax on high-frequency integration: the manual update-and-wait loop. They compound. Small pull requests keep queue batches cheap to rebuild when something is ejected, and the queue keeps small pull requests flowing without a human traffic controller. If you are heading toward trunk-based work, our piece on code review in trunk-based development covers the review side of that transition; the merge queue is the infrastructure that makes the merging side boring, which is the highest compliment infrastructure can earn.

Frequently asked questions

What problem does a GitHub merge queue solve?

Stale CI results. Two pull requests can each pass checks against an older main and still break it when both merge, because neither was tested against the other. The queue validates every pull request against the latest target branch plus the queued changes ahead of it, so what lands has been tested in the combination that will actually exist.

Do small teams need a merge queue?

Usually not. The queue earns its complexity when a busy branch takes many merges a day from many people, and CI takes long enough that results go stale before merging. A small team with fast CI can simply update branches before merging. Turn the queue on when update, wait, retry becomes a daily tax rather than an occasional chore.

Top comments (0)