Skip to content

PATTERN Cited by 1 source

Validate against the future state of main

Pattern

Before merging a PR, materialise a temporary branch that combines the PR with every other accepted PR ahead of it in a queue, and run a dedicated pipeline against that materialised future-state branch. Merge the PR only if the pipeline passes; otherwise eject the PR and let the queue continue.

The load-bearing reframing:

Before: "Does this PR work on its own (against the current main)?" After: "Does this PR still work alongside everything else about to land?"

(Source: sources/2026-04-29-atlassian-inside-atlassians-merge-queues)

When to apply

  • Scale threshold: merge frequency high enough that concurrent merges are the common case, not the exception. Atlassian's Jira repo discloses this boundary at ~300 merges/day with 800+ developers; semantic-merge-conflict-caused CI failures were 7–10% of all CI failures before the pattern was applied. (Source: sources/2026-04-29-atlassian-inside-atlassians-merge-queues)
  • Interacting PRs: refactors + feature work + dependency upgrades routinely touch code each other indirectly depends on. Independent PRs that collide logically even though they don't collide textually are the failure mode this pattern targets — the semantic merge conflict.
  • main-green is load-bearing: broken main stops everyone else's rebases, breaks local dev, and compounds firefighting.

Below this threshold, branch protection + rebase-required (everyone rebases on the latest main before merging) is typically adequate. Above it, it stops scaling because two rebased-against-the-same-main PRs can still collide if they both merge.

Shape

   PR approved + branch CI green
         ┌─────────┐
         │  Queue  │   ← PRs wait their turn per target branch
         └────┬────┘
   For each queued PR:
      (1) materialise `merge-queue-*` branch
          = PR + everything ahead of it in the queue
          per configured merge strategy
      (2) run dedicated merge-queue pipeline
          against that future-state branch
       ┌──────┴──────┐
       ▼             ▼
    pass:         fail:
    merge         eject PR
    into main     (keep queue running)

The three load-bearing structural pieces: queue (PRs wait their turn) + future-state branch (materialised with the configured merge strategy so what CI sees matches what will actually land) + dedicated pipeline (separate from post-merge pipeline so the pre-merge validation suite can be tuned for queue-latency).

(Source: sources/2026-04-29-atlassian-inside-atlassians-merge-queues)

Non-obvious design choices

Separate merge-queue pipeline from post-merge pipeline. These are deliberately different pipelines. The merge-queue pipeline is the faster suite that fits the queue's latency envelope (branch-CI equivalent + anything that detects inter-PR interaction failures). The post-merge pipeline — release-artefact build, deploy, full e2e — runs after merge as it always did. Atlassian's Bitbucket exposes this split via the merge-queues: section in bitbucket-pipelines.yml; the separation is load-bearing because bundling them would turn queue latency into a ceiling on release-pipeline cost. (Source: sources/2026-04-29-atlassian-inside-atlassians-merge-queues)

Build-concurrency > 1. A queue with concurrency=1 is a serialised merge discipline — correct but slow; the queue drains at pipeline-wall-clock pace. Concurrency > 1 runs multiple future-state pipelines in parallel. Each parallel pipeline validates a different-depth future-state (PR-A alone, PR-A+PR-B, PR-A+PR-B+PR-C, etc.); if PR-A fails, the later-in-queue pipelines' results are invalidated and re-run against the new head of queue. Jira sizes this at 14 against 300+ PRs/day. Concurrency sizing is the main throughput knob. (Source: sources/2026-04-29-atlassian-inside-atlassians-merge-queues)

Merge strategy choice is load-bearing. Merge commit / squash merge / rebase merge — whichever is configured, the temporary future-state branch is materialised with that strategy. Squash-merge future-state branches collapse PR history (easier to revert, harder to bisect); merge-commit future-state branches preserve per-PR commits (easier to bisect, more cluttered history). Jira picked merge-commit explicitly. (Source: sources/2026-04-29-atlassian-inside-atlassians-merge-queues)

Failed builds eject the PR, not the queue. See patterns/eject-failing-pr-keep-queue-running. Without this discipline the queue would stall on every failure and the pattern loses its throughput advantage.

Trade-offs

Upsides:

  • Near-zero semantic-merge-conflict-caused CI failures. Atlassian Jira: 7–10% → near zero.
  • Dropped incident rate. Atlassian Jira: 3–5 incidents/week → rare edge cases.
  • Developer satisfaction up. Atlassian Jira build-reliability satisfaction: 70% → 82%.
  • Merging becomes a background task. "Our engineers stopped thinking about merging altogether. They just queue and code." (Jira Head of Engineering.)
  • Clear authorship on break. Ejected PRs are attributable — the Slack debugging thread ("which PR broke main?") stops happening.
  • First merge SLO becomes feasible. The queue provides deterministic enough merge-latency that an SLO can be defined.

(Source: sources/2026-04-29-atlassian-inside-atlassians-merge-queues)

Downsides:

  • Extra CI capacity. Each PR now runs an additional pipeline — the merge-queue pipeline — on top of branch CI and post-merge CI. Build-concurrency sizing + merge-queue pipeline speed are load-bearing for keeping queue-latency bounded.
  • Temp-branch git churn. The merge-queue-* branch namespace generates git-reference churn on busy repos.
  • Hot-fix flow needs admin controls. Urgent fixes can't wait in the queue; admins need reorder / drain / deactivate controls to handle hot-fix + emergency scenarios.
  • Queue depth can grow unbounded under pipeline failure cascades. If multiple back-to-back ejections happen, queue latency spikes. Monitoring + alerting on queue depth is operationally load-bearing.

Composes with

Seen in

Last updated · 433 distilled / 1,256 read