Docs menu

Merge queue

.md

Queue branches instead of merging them: batched, speculatively tested, bisected on failure, so main only lands tested trees.

A repo's merge queue makes sure main only ever lands a tree CI has tested. You queue a branch instead of merging it. The queue tests it together with everything queued ahead of it, then lands it in order.

The merge queue is in preview. An organization owner or admin turns it on per repo.

Why a queue#

The CI merge gate tests each branch against the main it forked from. Two branches can each pass and still break main together: one renames a function, the other adds a new call to the old name. The queue tests the combination before either lands.

Using it#

oak push                      # the queue tests what's on the server
oak queue add                 # queue the current branch at that head
oak queue add fix-login --wait   # queue another branch and wait until it lands or fails
oak queue status              # what's in line, what's testing, recent results
oak queue status --json       # the same for scripts and agents
oak queue remove fix-login    # take a branch out of line

On the web, a branch's page has Add to queue in place of Merge (next to Merge when the queue isn't required), and Remove from queue once it's queued. The branches page shows the queue at the top, with what's testing and what landed or was ejected recently, and its batch bar becomes Queue selected.

Once the queue is on, oak merge onto main is refused with merge_queue_required (the web merge button too). oak merge --force still bypasses it, and the bypass is audit-logged.

Pushing to a branch while it's queued is fine. The queue re-tests the new head in the same place in line, and it never lands a head it hasn't tested.

What it does#

The queue was built to get many branches onto main with as little CI time as possible.

  • Batches. Several queued branches are tested in one CI run. When the batch passes, they all land with one squash commit each.
  • Speculation. Up to max_depth batches run at the same time. Each one is built on top of the batches ahead of it, so what CI tests is the exact tree main will have when that batch lands. A batch that finishes early waits for the ones ahead.
  • Adaptive batch size. The queue estimates how often a branch breaks CI from its recent results. It picks the largest batch that still passes at least half the time, up to max_batch. Waiting branches are spread across free slots, so you don't get one large batch while the other slots sit idle.
  • Bisection. When a batch fails, it's split in half and the first half is tested on its own. A passing half lands, and the culprit is found in about log₂(n) runs. Only the branch that breaks CI is ejected.
  • Results by tree. The queue remembers the CI verdict for every tree it tested. A tree that comes up again isn't re-run. During bisection, the second half tested on top of a passing first half is exactly the tree that already failed, so that answer is free.
  • Flaky tests. A branch tested alone gets flaky_retries extra runs (default 1) before it's ejected.
  • Cheap checks first. Merge conflicts and open review findings are found while the batch is put together, before any CI runs. A branch that only conflicts with a branch ahead of it in line waits instead of being ejected. The branch ahead might still fail and leave.
  • No duplicate CI after landing. If the batch already ran the workflows a merge onto main would run, landing doesn't run them again on the same tree.

Landing never relies on the speculation being right. For each branch, the queue replays the real squash merge and refuses to land unless the result is exactly the tree CI tested.

Workflows#

A batch runs as one commit, which isn't on any branch. Which workflows run:

  • If any workflow listens to merge_group, exactly those run, and they see a GitHub merge_group event. Use this when the checks you want before landing differ from what runs on main. main's own push workflows still run after landing.
  • Otherwise the workflows that a merge onto main would trigger run, and they see a push to main. paths: filters are measured against everything the batch would land.
on:
  merge_group:
    branches: [main]

If no workflow runs for a batch's tree, it passes right away.

Settings#

PUT /api/:owner/:repo/queue/config (org owner or admin):

FieldDefault
enabledfalseTurn the queue on.
require_queuetrueRefuse direct merges onto main while the queue is on.
max_batch8The largest batch (1–64).
max_depth3Batches tested at once (1–16). Each one uses a CI run.
flaky_retries1Extra runs a lone branch gets before it's ejected (0–5).

API#

GET    /api/:owner/:repo/queue                 queue, batches, recent results, stats
POST   /api/:owner/:repo/queue                 {"branch": "...", "priority": 0, "expected_head": "..."}
DELETE /api/:owner/:repo/queue/entries/:branch
GET    /api/:owner/:repo/queue/config
PUT    /api/:owner/:repo/queue/config

priority (−100 to 100) moves a branch ahead of lower-priority ones that aren't being tested yet. expected_head refuses with branch_moved unless the branch is still at that exact commit.

Something here wrong or missing? Run oak feedback -m "…" or email [email protected].