---
title: Merge
description: Register auto-merge, read the verdict on a pull request, and work with the merge queue.
llms_section: "For agents"
llms_order: 140
llms_summary: "Read when you merge a pull request, register auto-merge, read why a pull request is not ready, or use the merge queue."
---

# Merge

Register auto-merge once and Stamp merges the approved head when it is ready.

## Auto-merge

Register auto-merge once (only the PR's author can), then get the head
reviewed (see [Review and approve](/agents/review/#get-the-exact-head-reviewed)). Stamp merges the current head once it is approved and
ready; you do not poll.

```sh
stamp pr merge NUMBER --repo OWNER/REPO --auto --head HEAD_SHA --base BASE_SHA
```

The registration follows later pushes to the branch (nothing to re-run),
but each new head needs its own approval before it merges; on a merge-queue
base it is enqueued at the current head instead. `--auto --status` shows the
head it waits on; it ends when the PR is merged or closed, on
`--auto --cancel`, or after 14 days.

On a merge-queue base, registering does not enqueue by itself: the PR
enters the queue when its auto-merge check finds it ready, and the queue
is first in, first out by that entry time, not by registration time. PRs
registered at about the same moment have no guaranteed order between them
(a check that finds the auto-merge lease busy is retried a few seconds
later). Read where a request stands with
`stamp pr queue status NUMBER --repo OWNER/REPO`: `autoRequeue: true`
on a stale request means the queue puts the same request back itself,
position kept; do not re-enqueue. A landed request reports
`memberCommit` (this PR's own commit on the base) and `batchTip`
(where the base ended after the whole batch); `landingSha` is the batch
tip, kept for older readers. GitHub shows a queue-landed PR as closed, not
merged (Stamp publishes the batch commit itself), so `is:merged` and
`gh pr list --state merged` miss it; it carries the `stamp-merged` label
instead: `gh pr list --state closed --label stamp-merged`, or search
`is:closed label:stamp-merged` (a plain `--label` lists only open PRs).

To find the queue candidate run that tested a PR (anyone's, not only
yours: batches are repository state, while queue requests stay their
requester's), run `stamp pr checks NUMBER --repo OWNER/REPO`: after the
PR's own checks it prints `merge queue runs:`, one line per recent batch
with the PR in it (outcome, batch id, `stamp-mq/` branch, run id, reason),
newest first, and under a failed one the command that prints its failed
jobs: `stamp run view RUN_ID --repo OWNER/REPO --log-failed`. The same
batches are `queueRuns` in `pr view --json` (omitted when there are none
or the queue is not yours to read; `pr checks --json` stays the bare
checks array). `stamp run list --repo OWNER/REPO --branch
stamp-mq/BATCH_ID` lists a candidate branch's runs.

A base that is a registered merge-queue target is never merged directly,
by auto-merge or by an agent, even while its queue is not enabled (saved
but not yet enabled, or paused). The verdict then reads
`QUEUE_UNAVAILABLE`, never READY. Auto-merge waits: `--auto --status`
says `waiting for the merge queue on BRANCH to be enabled`
(`waitingOn: QUEUE_UNAVAILABLE`), you are told once, and it is enqueued
as soon as the queue is enabled or resumed. An agent's `stamp pr merge` there is refused
with `QUEUE_TARGET_NOT_ENABLED`; a person can still merge by hand. The
notice, the refusal and the status name why in `queueNotEnabledReason`,
and the next step follows from it: `verified_not_enabled` (a repository
admin runs `stamp queue enable OWNER/REPO`), `not_verified`
(`stamp queue verify OWNER/REPO --pr NUMBER`, then `enable`), `paused`
(a repository admin resumes it on the queue page; agents cannot, and
`stamp queue enable` is refused with `BATCH_QUEUE_PAUSED`),
`pause_unconfirmed` (stored before Stamp recorded pauses: a repository
admin confirms on the queue page; an agent's enable is refused with
`BATCH_PAUSE_UNCONFIRMED`),
`other_installation` (set up under another installation; set it up
again), and `disabled`, `retired`, `not_configured`, `state_unreadable`.
`stamp pr queue status` says `queueState` PAUSED for an admin's pause
(or an unconfirmed one), NOT_ENABLED for a queue saved and not enabled
yet. Turning the queue on checks up to 25 of the repository's waiting
registrations at once, oldest first. The rest wait for the fallback
sweep: every 10 minutes it checks one page of 50 of up to autoMergeScan
(20000) live registrations (all repositories, oldest first by
registration write — a safety ceiling raised from 500, review of
#1239 item 2: no cost per run beyond a bigger result set, since the
store's filtering scan already covers every matching row regardless
of this limit; GitHub reads and merges stay capped at 50 per run
either way), so with N of them the worst-case wait until a
registration is CHECKED (not merged: merging still needs its approvals
and checks) is `ceil(N / 50) x 10` minutes, strictly conditional on N <= autoMergeScan
AND N holding steady between runs (ordinary churn): that bound is
exact there, for any N. While N (within the ceiling) is itself
climbing every run with nothing finishing, the bound is not exact —
the rotation's page count is derived from the live count each run, so
a registration can wait longer than `ceil(N / 50)` for as long as the
climb continues, recovering once it stops. Above the ceiling there is
no guarantee: registrations ranked past autoMergeScan are not swept
until the pool shrinks below it. Event-driven checks (a
registration's own deliveries) are unaffected by this limit.
If Stamp cannot read whether the base is a queue target, auto-merge waits
a pass (`QUEUE_STATE_UNKNOWN`) and a direct merge is refused with
`QUEUE_STATE_UNAVAILABLE` (nothing was merged; retrying is safe).
If the queue refuses the enqueue for a reason that will not change on its
own (`batch_workflow_changed`, `batch_workflow_invalid`,
`batch_merge_conflict`), the registration ends (`QUEUE_REFUSED`, with the
reason in `--auto --status`) and you are told once with the next step:
change the PR and register again, or merge it directly
(`stamp pr merge NUMBER --repo OWNER/REPO --head SHA --base SHA` once
that head is approved, by your self-approval with evidence (see [Review and approve](/agents/review/#get-the-exact-head-reviewed)) or by
another reviewer, or a person). Any other refusal is waited out.

## The verdict on a PR

`stamp pr view` prints Stamp's verdict under the title, pinned to the head and
base it was decided for, and names who approved that head. The first state
that applies wins: `MERGED`, `CLOSED`, `DRAFT`, `CONFLICT`, `CI_UNREADABLE`,
`CI_FAILING`, `CI_NOT_EXECUTED` (a required check never reported, or was skipped
in a workflow run where a job failed or was cancelled), `CI_PENDING`, `REVIEW_UNREADABLE`,
`CHANGES_REQUESTED`, `STALE_REVIEW`, `NEEDS_REVIEW`, `QUEUE_READY` (approved;
it lands through Stamp's merge queue), `BEHIND` (also when the branch must be
up to date and the base moved, while GitHub still says clean), `BLOCKED` (GitHub isn't allowing
the merge yet; the verdict's `blockedReasons` name what it waits on when the
evidence shows it: `REVIEWS_REQUIRED` "1 of 2", `CODEOWNERS_REVIEW_REQUIRED`,
`LAST_PUSH_APPROVAL_REQUIRED`, `CHANGES_REQUESTED`, `SIGNED_COMMITS_REQUIRED` (under
the current merge method), `LINEAR_HISTORY_REQUIRED`,
`CONVERSATION_RESOLUTION_REQUIRED`, `REQUIRED_CHECK_MISSING`,
`DEPLOYMENT_REQUIRED`, `BRANCH_BEHIND`; empty when GitHub does not say),
`OPTIONAL_CHECK_FAILING` (approved and every required check passed, but GitHub
says "unstable" because checks outside the required set did not pass; the
verdict's `optionalFailing` names them. It does not clear on its own:
auto-merge waits for a clean state and tells the author once, so fix or re-run
those checks, or merge by hand with `stamp pr merge`),
`UNCONFIRMED` (GitHub does not confirm a clean merge yet, including a "blocked"
in the two minutes after the last check completed, while GitHub recomputes, and
a merge state GitHub has not computed yet; Stamp asks GitHub again and
re-checks shortly), `QUEUE_UNAVAILABLE` (it would be READY, but its base is a
registered merge-queue target whose queue is not enabled: it waits for the
queue; a person can still merge by hand), `READY`.

A required check the repository's workflow reported as skipped counts as
satisfied, as it does on GitHub, unless a job in the same workflow run failed or
was cancelled (then the skip may be a broken `needs:`, not a rule). READY also needs GitHub's own mergeable state
to be clean.

A failure in any run on the head counts toward `CI_FAILING`, including
manually dispatched (`workflow_dispatch`) runs, even when GitHub says clean;
re-running the failed jobs clears it, because only the newest attempt of each
run counts.

## Merge queue (agents)

An agent can set up, verify and enable Stamp's merge queue for a branch, with
the same discovery, confirmation and verification as the settings page:

```sh
stamp queue setup OWNER/REPO --branch BRANCH [--workflow ID]
stamp queue save OWNER/REPO --branch BRANCH --workflow ID --ci-policy head|all --max-batch N --wait SECONDS --timeout SECONDS [--protect GLOB... | --protect-none] --confirm
stamp queue verify OWNER/REPO --pr NUMBER
stamp queue enable OWNER/REPO
stamp queue status OWNER/REPO
```

`setup` lists the workflows, the jobs it detected, the branch-required checks
and the digest it would confirm. `--workflow` names the primary (Test)
workflow only: branch-required checks it lacks are found in other workflows run
on the same head (listed as `additional workflow:`), sampled from a recent green
run whose pull request is still open, and every candidate must pass them too. `save` reads that discovery again and shows
what it confirms. `--protect GLOB` (repeatable) replaces the queue's protected
path list and `--protect-none` clears it; a `save` with neither keeps the stored
list. `verify` runs the saved CI on an approved pull request without
merging it; `enable` only succeeds once that verification passed.
`verify` accepts a GitHub approval or a Stamp approval from anyone but the author at the pull request's current head.

`status` (`--json`: the same object) names the active batch and two facts, each
null when it does not apply. `baseWait`: the active batch failed on a red
target branch and waits for the branch's own CI to recover (`batchId`, `base`
commit, `workflowId`, `runId`, `runUrl` of the red run, `since`,
`waitingSeconds`, `nextCheck`); nothing to re-enqueue, the target branch's CI
must pass. `stalled`: the queue should move and does not (`reason`
`batch_overdue` | `base_wait_long` | `waiting_no_batch`, `since`, `batchId`,
`ageSeconds`); the requesters of the affected requests get one
`merge_queue_stalled` notice per stall. Do not re-enqueue; ask the repository
maintainer or the Stamp operator.

Changing the CI through the queue. A pull request that changes what judges the
queue (a configured workflow such as `.github/workflows/test.yml`, anything
under `.github/actions`, or a local action or reusable workflow a configured
workflow uses) is not refused: it is tested alone, in a batch of its own, and
judged by the workflow as its own merged tree defines it (it carries a
non-author approval at its exact head, so the CI change was reviewed). Its
receipt (`stamp pr queue status`), its notification, the draft candidate and
the queue page say "tested alone because it changes the CI definition (PATH)".
When it lands, the queue re-pins its workflow to the new definition by itself;
the same happens when the workflow changes on the target branch outside the
queue (the queue's audit records `workflow_repinned:PATH:OLD->NEW`). Only when
a configured workflow file is gone, or no longer runs on pull requests or
names the required checks, do requests fail with `batch_workflow_invalid`
naming the file: a repository admin then runs `stamp queue setup`, `save`
and `verify` again. A candidate that would leave the workflow in that state is
refused the same way. Protected paths (`--protect`) are treated the same: a
pull request changing one is tested alone, and the receipt names the file.
Only a verification refuses either (`batch_workflow_changed` naming the file):
verify with a pull request that changes neither.

Merging around the queue. A registered target lands only through its queue:
while the queue is not enabled (after `save` until it is enabled, or while
paused) an agent's direct merge is refused with `QUEUE_TARGET_NOT_ENABLED`
and auto-merge waits (`QUEUE_UNAVAILABLE`) instead of merging; a person can
still merge. An auto-merge registration whose enqueue the queue refuses for
good (`batch_workflow_changed`, `batch_workflow_invalid`,
`batch_merge_conflict`) ends (`QUEUE_REFUSED`) and its registrant is told
once, so a later change of the queue's state never merges it silently.

The boundary: only a registered queue target (a repository and branch the
Stamp operators registered in a reviewed deployment change) of a repository
your server's GitHub App covers, and only Stamp's own queue mode. Anything
else is refused with `QUEUE_TARGET_NOT_REGISTERED`, `REPOSITORY_NOT_COVERED` or
`QUEUE_MODE_AGENT_UNSUPPORTED`; `status` lists the registered targets.
