# Merge queue

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](/docs/merging#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

```bash
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](/docs/reviewing) 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.

```yaml
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):

| Field | Default | |
|---|---|---|
| `enabled` | `false` | Turn the queue on. |
| `require_queue` | `true` | Refuse direct merges onto `main` while the queue is on. |
| `max_batch` | `8` | The largest batch (1–64). |
| `max_depth` | `3` | Batches tested at once (1–16). Each one uses a CI run. |
| `flaky_retries` | `1` | Extra runs a lone branch gets before it's ejected (0–5). |

## API

```text
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.
