--- slug: merge-queue type: pattern summary: "Serialize approved changes through an ordered queue that tests each one against the changes ahead of it, so concurrent merges that pass alone but break together never reach the mainline." created: 2026-06-18 updated: 2026-06-18 related: continuous-integration: relation: extends note: "CI validates each change in isolation; a merge queue extends that guarantee to concurrent changes by testing them against each other before they land." agentic-pull-request: relation: uses note: "The agent-authored pull request is the unit that flows through the queue." agent-sprawl: relation: mitigated-by note: "A queue absorbs the concurrent-merge load that parallel agents produce, instead of letting it break the mainline." back-pressure: relation: related note: "A saturated queue is back pressure on merge throughput: a slow signal that the team is producing changes faster than they can be safely landed." rollback: relation: complements note: "Rollback is the safety valve for the bad batch that slips through anyway." continuous-delivery: relation: enables note: "A reliably green mainline is the precondition for shipping on demand." progressive-delivery: relation: complements note: "The queue governs what lands on the mainline; progressive delivery governs how that landed change reaches users." pipeline-as-code: relation: uses note: "Queue behavior is configured as branch protection in the same pipeline config that defines the checks." --- # Merge Queue > **Pattern** > > A named solution to a recurring problem. *Serialize approved changes through an ordered queue that tests each one against the changes ahead of it, so concurrent merges that pass alone but break together never reach the mainline.* *Also known as: Merge Train, Bors, Mergify* Two pull requests both pass every check. Both get approved. Both merge within the same minute. The mainline goes red anyway. Neither change was wrong; they were tested against the old mainline, not against each other, and the combination broke something neither author could see. A merge queue sits between "approved" and "merged" and refuses to let that happen. It puts approved changes in a line and tests each one against the state it'll actually land on, including the changes ahead of it. ## Understand This First - [Continuous Integration](continuous-integration.md) — the queue is the discipline that makes "always-green mainline" survive concurrency. - [Agentic Pull Request](agentic-pull-request.md) — the unit of change that flows through the queue. ## Context This is an **operational** pattern that sits one layer above [Continuous Integration](continuous-integration.md). CI answers a single question well: does this change pass on its own? It tests each pull request against the mainline as it was when the branch was cut. The gap it leaves open is concurrency. When several approved changes wait to merge at the same time, each one was validated against a mainline that did not yet include the others. CI verified them in parallel universes; the mainline is one universe, and that is where they collide. In agentic coding the gap widens fast. A single developer directing a handful of agents can have several pull requests approved and ready within an hour, all branched from roughly the same commit, all green, none aware of the rest. The bottleneck moves from writing the change to landing it without the mainline going dark. The merge queue is the machinery that turns "many approved changes" back into "one safe sequence." ## Problem Continuous integration promises a green mainline, but it only tests changes against the past. Two changes that each pass against the current mainline can break it when combined, because neither was tested against the other. This is a semantic merge conflict: there's no textual conflict for Git to flag, so the merge succeeds cleanly and the build fails a minute later. One author renames a function; another adds a caller of the old name on a branch cut before the rename. Both branches are green. The merge is red. You can serialize merges by hand, asking everyone to rebase and re-run CI before landing, but that turns the team into a queue managed by Slack messages, and it gets slower exactly when change volume is highest. How do you keep a fast-moving mainline green when many approved changes want to land at once? ## Forces - A change validated against the current mainline is not validated against the other changes waiting to land beside it. - Re-testing every change against the live mainline immediately before merge is correct but serializes the team and wastes the parallel CI you already paid for. - Merging one change at a time is safe but slow; merging in large batches is fast but makes the failing change harder to find. - One bad change in a batch can hold every good change behind it hostage if the system stops to fix it in place. - The faster changes arrive, the more the queue itself becomes a bottleneck, and a backed-up queue is its own kind of signal. ## Solution **Put approved changes in an ordered queue, test each one against the mainline state that includes the changes ahead of it, and let only the tested result merge.** Approval no longer merges a change; it admits the change to the line. The queue then builds a candidate that stacks the pending changes in order and runs the checks against that candidate, so what gets tested is what will actually land, not what each branch saw in isolation. When a candidate passes, it merges and the next one advances. When a candidate fails, the queue ejects the change that broke it and re-forms the line around the survivors, so a single bad change doesn't block the good ones behind it. The author of the ejected change fixes it and rejoins the back of the line. The mainline only ever sees a state that was tested as a whole. The throughput dial is **batch size**: how many queued changes the system tests together as one speculative candidate. Test them one at a time and every merge is unambiguous but the line moves slowly. Test thirty at once and the line moves fast, but when the batch fails the system has to work out which change is at fault, usually by bisecting the batch into smaller candidates. Most teams let the queue widen batches when the line is long and the mainline has been healthy, and narrow them when failures start appearing. The principle is the same whatever the tool calls it: nothing merges until it has been tested against everything ahead of it in line. > **💡 Tip** > > Size your batches to your failure rate, not your impatience. If your queued changes rarely conflict, large speculative batches give you most of the throughput of merging everything at once with none of the green-alone-red-together risk. If conflicts are common, smaller batches find the culprit faster and keep the line moving. Watch the eject rate: a queue that ejects often is telling you the changes entering it are too coupled or too stale. ## How It Plays Out A platform team runs four agents in parallel against a shared service, each closing a different ticket. By mid-afternoon all four pull requests are approved and green, branched from the same morning commit. Without a queue, whoever merges last wins the race and discovers at the next push that agent two's new database column and agent four's migration assumed different default values: clean Git merge, red mainline, an hour of bisecting to find which pair conflicted. With a queue, the four changes enter the line in approval order. The queue tests change one alone, change two atop one, change three atop one and two, and stalls when the candidate carrying both the column and the migration fails. It ejects change four, re-forms the line around the three that pass, and merges them. The developer gets a single targeted notification: change four conflicts with the migration ahead of it. Nobody's bisecting a green mainline that went dark for no visible reason. > **⚠️ Warning** > > A merge queue is not a license to skip review or to let agents merge unattended. It guarantees that what lands was tested together, not that what lands is correct: a change can be green against everything ahead of it and still ship a defect that no test covers. The queue can't read intent; it only checks that the pieces fit. The queue protects the *mainline's* health, not the *change's* judgment. Keep human approval at the front of the line and [Rollback](rollback.md) ready at the back. ## Consequences **Benefits.** The mainline stays green under concurrency, which is the precondition for shipping on demand and the thing CI alone cannot promise once more than one change is in flight. Semantic merge conflicts surface in the queue, against a named candidate, instead of on the mainline after the fact, so the failing change is identified rather than hunted. The queue absorbs the concurrent-merge load that parallel agents produce, turning a race condition into an ordered, automatic sequence. And the line gives the team an honest throughput signal: how long a change waits to land is a number you can watch. **Liabilities.** The queue adds latency between approval and merge, because a change now waits its turn and is tested against the line rather than merged immediately. Speculative testing spends CI capacity on candidates that may be discarded when a change ahead of them fails. Large batches buy throughput at the cost of harder failure attribution, and tuning batch size is a real operational decision, not a default you can ignore. A saturated queue becomes [Back Pressure](back-pressure.md) on the whole team: when changes arrive faster than they can be safely landed, the line grows. The right response is to slow the producers or speed the checks, not to widen batches until failures hide in them. ## Sources - Graydon Hoare and the Rust project's "Not Rocket Science Rule," implemented as the Bors bot, established the foundational discipline: never merge a change to the mainline until an automated system has tested the exact merged result. Bors and its successor `bors-ng` made queued, tested-before-merge integration practical for open-source projects and named the failure mode it prevents. - The merge-queue model was generalized and productized by the hosted-CI and code-review community through the late 2010s, which built ordered queues with speculative batching, automatic conflict ejection, and re-forming lines on top of the same rule, and reached general availability in mainstream code-hosting platforms. - The "merge train" framing for the same idea, batching queued changes and testing each against the ones ahead, emerged from the GitLab CI community as a parallel name for the pattern. --- - [Next: Continuous Delivery](continuous-delivery.md) - [Previous: Continuous Integration](continuous-integration.md)