SuperAPI AI Directory

mergify-merge-queue

Deployment & operations

View repository

Updated 10-16 00:29

About this skill

Mergify Merge Queue

Overview

The merge queue serializes PR merges, running CI on temporary merge commits to catch integration failures before they reach the target branch. Use comments on the PR to queue/dequeue it, and the CLI to monitor queue state, inspect individual PRs, and manage the queue.

mergify queue show <PR> reports on a PR that is no longer in the queue too — whether it was dequeued, merged by the queue, or never queued, and why — so start there for a PR that vanished from the queue. See Diagnosing a dequeued PR; the GitHub-side surfaces documented there are the fallback for when the CLI cannot read the activity log.

Queuing and Dequeuing a PR

Queue, dequeue, and requeue actions are driven by comments on the pull request, not the CLI:

Comment Effect
@mergifyio queue Add the PR to the merge queue (also use to requeue a PR that was dequeued)
@mergifyio dequeue Remove (dequeue) the PR from the merge queue

@mergifyio requeue is accepted, but it is a deprecated alias that runs the same command as @mergifyio queue — there is no separate requeue behavior. Post @mergifyio queue.

When Mergify processes the comment, it adds a 👍 (thumbs up) reaction to the comment to acknowledge receipt. After queuing, use mergify queue show <PR_NUMBER> to watch the PR's status as it progresses through the queue.

Commands

mergify queue status                 # Show queue status (batches, waiting PRs)
mergify queue status --branch main   # Filter by branch
mergify queue status --json          # Machine-readable JSON output
mergify queue show <PR_NUMBER>       # Detailed state of a PR in the queue
mergify queue show <PR_NUMBER> -v    # Full checks table and conditions tree
mergify queue show <PR_NUMBER> --json # Machine-readable JSON output
mergify queue pause --reason "..."   # Pause the queue (requires reason)
mergify queue unpause                # Resume the queue

That is the whole queue group: status, show, pause, unpause. There is no subcommand for dequeuing a PR — the dequeue reason comes from queue show on a PR that has left the queue, not from a flag. For the full queue trail (every enter / checks / leave event, not just the last exit), use mergify events --pr <PR> --since 90d — see the mergify-events skill.

Is the PR queued, dequeued, or never queued?

Start here — the rest of the workflow branches on this answer.

mergify queue show <PR> answers all four cases. A PR with no queue entry is a normal answer, not an error: the command prints a notice and exits 0 in every case below.

queue show result Meaning
PR #N block with position / CI state The PR is in the queue
PR #N was dequeued <when> + a Dequeue code It left the queue without merging — see the reason table
PR #N was merged by the merge queue <when> It left the queue by merging. Not a dequeue
PR #N is not in the merge queue No queue activity in the retained window — never queued, aged out, or not a real PR number

Under --json, a PR that is not currently queued carries queued: false plus a dequeued discriminator:

mergify queue show 1234 --json | jq -e '.queued == false' >/dev/null && echo "not in queue"
  • dequeued: true — left without merging; the raw leave event is under queue_leave
  • dequeued: false — merged by the queue, or never queued (queue_leave tells the two apart: null means never queued)
  • dequeued: null — the lookup itself failed, and queue_leave_error says why. Not the same as "never queued" — fall back to the GitHub-side surfaces rather than concluding anything

queue_leave_head_sha carries the head the diagnosis describes. Compare it against the PR's current head before reporting anything from it — a dequeue is often caused by a push, so its failing checks routinely belong to a commit the PR no longer has:

mergify queue show 1234 --json | jq -r '.queue_leave_head_sha'   # 31b4a485b8ce…
gh pr view 1234 --json headRefOid -q .headRefOid                 # 340361aae580…  → superseded, stay quiet

Two limits to keep in mind. History goes back 90 days (the activity log's retention), so an older dequeue reads as "no activity". And the command does not check that the PR exists, so a typo'd number prints the same "not in the merge queue" notice — confirm the PR is real (gh pr view <PR>) before concluding it was never queued.

Do not use the presence of a Mergify Merge Queue check run as the test — a PR that merely matches the queue conditions gets one titled Waiting for queue conditions without ever being queued. A # Merge Queue Status comment is a reliable positive signal (the pre-queue "Queue this pull request" offer comment deliberately carries no such heading), but its absence proves nothing, since the comment can be disabled per repository.

Diagnosing a dequeued PR

Four surfaces carry the reason. Prefer them in this order.

1. mergify queue show <PR> (start here)

On a PR that is no longer queued, queue show reads the PR's last merge-queue exit and renders it:

PR #37823 was dequeued 24m ago

  Dequeue code:  CHECKS_FAILED
  Queue:         default
  Queued at:     46m ago
  Trigger:       merge queue internal
  Head SHA:      31b4a48

  The merge conditions cannot be satisfied due to failing checks

  - `@github-actions/all-greens`

  Failing checks:
    ✗ all-greens  failure
      https://github.com/Mergifyio/monorepo/actions/runs/…/job/…

  Fix the cause above, then comment `@mergifyio queue` on the pull request.

That is the engine's own explanation plus the failing checks with their job-log URLs — go straight to the failing job rather than hunting for it. The explanation is capped at 12 lines in compact mode; add -v for the whole thing (a PR_DEQUEUED reason embeds the full unmet-condition tree, which runs to dozens of lines).

Head SHA is the commit all of that describes. If it is not the PR's current head, the report is about a commit that has since been replaced — the checks above may already be green again. Check it before acting on the failure.

--json gives the same thing machine-readably: dequeued, plus queue_leave carrying the raw event, so read dequeue_code, reason, and unsuccessful_checks[].details_url from .queue_leave.metadata. Use the promoted queue_leave_head_sha for the staleness check above.

Reading the activity log is best-effort: a token scoped to the merge queue can be refused the repository's event log (403). The command then degrades to the plain "not in the merge queue" notice plus a warning on stderr, and reports dequeued: null — that is the signal to fall through to the surfaces below, not a statement that the PR was never queued.

2. The Mergify activity log (what surface 1 reads underneath)

GET /v1/repos/{owner}/{repo}/logs returns the queue lifecycle events, newest first. queue show calls this for you, and mergify events --pr <PR> --since 90d (the mergify-events skill) browses the whole lifecycle from the CLI — including every event type, not just queue ones. Go direct with curl only when it degrades (403 above, with a token that can read the log) or from somewhere the CLI is not installed:

MERGIFY_TOKEN must hold a Mergify token: a GitHub token still works against the Mergify API but is deprecated, and the credential mergify auth login stores lives in the OS keychain rather than the environment, so a raw curl cannot reach it.

REPO=owner/repo
PR=1234
FROM=$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-90d +%Y-%m-%dT%H:%M:%SZ)

curl -sS -H "Authorization: Bearer ${MERGIFY_TOKEN:?set a Mergify token}" \
  "https://api.mergify.com/v1/repos/$REPO/logs?pull_request=$PR&event_type=action.queue.leave&received_from=$FROM" \
| jq 'if .events == [] then "no leave event in window — never queued (or aged out)"
      else .events[0].metadata
           | {merged, dequeue_code, reason,
              failing: [.unsuccessful_checks[]? | {name, state, details_url}]}
      end'
{
  "merged": false,
  "dequeue_code": "CHECKS_FAILED",
  "reason": "The merge conditions cannot be satisfied due to failing checks\n\n- `ci-gate`",
  "failing": [
    {
      "name": "ci-gate",
      "state": "failure",
      "details_url": "https://github.com/owner/repo/actions/runs/28589756829/job/84771312071"
    }
  ]
}

Read it as:

  • "events": [] / size: 0 → no leave event in the window → the PR was never queued (or the event aged out; see the window rule below).
  • merged: true → it left the queue by merging. Not a dequeue.
  • merged: false → it was dequeued; dequeue_code says why (see the reason table).
  • unsuccessful_checks[].details_url → direct link to the failing CI job log. This is how you reach the CI failure after the PR has left the queue.

Two traps that make this silently return nothing:

  • received_from is required in practice. The window defaults to the last 24 hours. A dequeue from last week returns size: 0 with no error, which reads exactly like "never queued". Always pass received_from.
  • The window may not exceed 93 days (retention is 90 days) or the call fails with 422 'received_from' and 'received_to' cannot span more than 93 days.

Same endpoint, other useful filters: &outcome=failure restricts leave events to dequeues (a merge is success); drop event_type to see the whole lifecycle (action.queue.enter, checks_start, checks_end, leave).

3. The Mergify Merge Queue check run

The check-run title names the reason directly, and its summary is the full queue report:

SHA=$(gh pr view $PR --repo $REPO --json headRefOid -q .headRefOid)
gh api "repos/$REPO/commits/$SHA/check-runs" \
  -q '.check_runs[] | select(.name=="Mergify Merge Queue") | {conclusion, title: .output.title, summary: .output.summary}'

Titles map to state without any parsing:

output.title State
Dequeued — <reason> (conclusion neutral) Dequeued, reason in the title
Dequeued from merge queue (conclusion neutral) Dequeued, but the reason did not resolve to a named code — use surface 1
Merged via merge queue (conclusion success) Merged by the queue
Waiting for queue conditions, Checks …, In merge queue Still in the lifecycle

Caveat: the check run lives on the head SHA it was written against. If the dequeue was caused by a push (PULL_REQUEST_UPDATED, DRAFT_PULL_REQUEST_CHANGED), the current head has a fresh check run and the dequeue report sits on the previous SHA. Use surface 1 or 4 in that case — surface 1 names the SHA (Head SHA / queue_leave_head_sha), so it is the one that lets you detect the mismatch rather than fall into it. Note also that gh pr view --json statusCheckRollup returns a null title — go through gh api .../check-runs as above.

4. The # Merge Queue Status comment

mergify[bot] posts one comment per queue session, so read the last one. It survives pushes, which makes it the most robust GitHub-side surface.

gh api --paginate --slurp "repos/$REPO/issues/$PR/comments" \
| jq -r '[.[][] | select(.user.login=="mergify[bot]")
              | select(.body|contains("# Merge Queue Status"))] | last | .body'

--paginate matters: the endpoint returns 30 comments per page and the newest are on the last page, so without it last silently hands you a stale status comment (or none) on any PR with real discussion. --slurp collects the pages into an array of arrays — hence .[][] to flatten — and is incompatible with -q, so the filter goes through jq instead.

Its structure, in order: a hidden JSON payload, a timeline, the merge conditions, then ## Reason, Failing checks: (each with a [job log] link), and ## Hint. The hidden payload gives the state without parsing prose:

<!--- ... {"version": 1, "state": "dequeued", "queue_rule_name": "default", ...} ... -->

state is one of waiting, checking, frozen, bisecting, merged, dequeued. It does not carry the dequeue code — that is in the ## Reason prose below it.

Caveat: this comment can be turned off per repository (merge_queue.status_comments: none, or outcomes for terminal events only). Absence of a comment does not prove the PR was never queued.

Dequeue reasons and what to do next

dequeue_code values and the action they call for. The engine ships a per-reason ## Hint in the report — for a code not listed here, read that Hint rather than guessing.

dequeue_code What happened What to do next
PR_MERGED Merged by the queue Nothing — this is success
PR_MANUALLY_MERGED Merged outside the queue Nothing
CHECKS_FAILED Required checks failed on the merge commit Read unsuccessful_checks[].details_url, fix the CI. Pushing a fix requeues it automatically once conditions match again; if it was flaky, requeue as-is with @mergifyio queue
CHECKS_TIMEOUT checks_timeout elapsed before conditions were satisfied Check the reason's details: checks that never reported mean the check names in your conditions don't match what CI publishes (fix the config, not the PR). Checks still running mean CI is too slow or stuck
PULL_REQUEST_UPDATED Someone pushed to the PR while it was queued Stop pushing to a queued PR. Requeue when the branch is final
DRAFT_PULL_REQUEST_CHANGED The queue's draft/batch PR got commits Mergify did not create Never push to the merge-queue draft branch. Requeue the original PR
CONFLICT_WITH_BASE_BRANCH The PR conflicts with its base branch Rebase or merge the base branch, resolve conflicts, then requeue
CONFLICT_WITH_PULL_AHEAD The PR conflicts with a PR ahead of it in the queue Wait for the PR ahead to merge, then rebase and requeue
BRANCH_UPDATE_FAILED Mergify could not update the PR's head branch Read the reason details, update the branch yourself, requeue
BASE_BRANCH_MISSING / BASE_BRANCH_CHANGED The base branch is gone or changed Retarget the PR to a live base branch, then requeue
PR_MANUALLY_DEQUEUED A human removed it (command, dashboard, or API) The reason names who and how. Requeue only once you know why they pulled it
PR_DEQUEUED Queue conditions stopped matching Look at the conditions in the report; fix the PR or requeue
DROPPED_BY_BISECTION_ELIMINATION Bisection blamed other PRs and dropped this one untested It is unproven, not known-broken. Requeue to test it on its own
STACK_PREDECESSOR_DEQUEUED A predecessor in the same stack was dequeued Fix the predecessor, requeue the stack
QUEUE_RULE_MISSING / CONFIGURATION_CHANGED The config changed under the queued PR Fix .mergify.yml (see the mergify-config skill), then requeue
INCOMPATIBILITY_WITH_BRANCH_PROTECTIONS Queue settings clash with branch protections Reconcile the repository's branch protections with the queue config — requeuing alone will not help
UNPROCESSABLE_PULL_REQUEST Too many check runs, comments, or files for Mergify to process Shrink the PR

Not every code means the PR left the queue. These reasons interrupt the checks and the PR stays queued — do not treat them as a dequeue and do not requeue:

PR_AHEAD_DEQUEUED, BATCH_AHEAD_FAILED, PR_WITH_HIGHER_PRIORITY_QUEUED, MERGE_QUEUE_RESET, SCHEDULED_FREEZE_STATUS_CHANGED, SPECULATIVE_CHECK_NUMBER_REDUCED, INTERMEDIATE_RESULTS_SKIPPED, CHECKS_RETRIED, BATCH_SCOPES_CHANGED, SCHEDULE_BLOCKED_AHEAD_YIELDED, PR_CHECKS_STOPPED_BECAUSE_MERGE_QUEUE_PAUSE

They arrive as abort_code on an action.queue.checks_end event (with aborted: true) rather than as dequeue_code on a leave event, and the check-run title reads Checks restarted — … or Checks aborted — … rather than Dequeued — …. The authoritative test for "did it actually leave the queue" is an action.queue.leave event with merged: false — not the presence of a code from this list.

Checking Queue Status

Use mergify queue status to see the current state of the merge queue:

  • Batches: groups of PRs being tested together, shown with their CI status and ETA
  • Waiting PRs: PRs queued but not yet in a batch, shown with priority and queue time
  • Pause state: whether the queue is paused and why

Use --json when you need to parse the output programmatically.

Inspecting a PR in the queue

Use mergify queue show <PR_NUMBER> to check why a PR is stuck or how it's progressing:

  • Position: where the PR sits in the queue
  • Priority: which priority rule matched
  • CI timeout: when the queue will give up on the PR's checks (- when no timeout is configured) — watch this to catch a CHECKS_TIMEOUT before it fires
  • CI state: whether checks are passing, pending, or failing
  • Conditions: which conditions are met and which are blocking
  • Use -v (verbose) for the full checks table and conditions tree

-v lists check names and states only — no links to the CI jobs. For job-log URLs on a PR that is still queued, use the GitHub-side surfaces above (the check-run summary and the status comment); once the PR has left the queue, queue show itself prints them. --json is a raw passthrough of the API payload, so it carries one more field the human render drops: queue_rule (the resolved queue rule config, not just its name).

Queue States

State Meaning
running Batch is actively running CI
preparing Batch is being set up
bisecting Batch failed, bisecting to find the culprit
failed CI failed for this batch
merged PRs in this batch have been merged
waiting_for_merge CI passed, waiting for GitHub to merge
waiting_for_previous_batches Blocked on earlier batches completing
waiting_for_batch Waiting to be picked up into a batch
waiting_for_requeue A batch ahead failed; this batch will be re-embarked
waiting_schedule Outside the configured merge schedule
frozen Queue is paused

Pausing and Unpausing

Pause the queue to temporarily halt all merges (e.g., during incidents or deployments):

mergify queue pause --reason "production incident — halting merges"
mergify queue unpause
  • Pausing does not cancel running CI — it prevents new merges from starting
  • The reason is visible to all team members in the queue status
  • Use --yes-i-am-sure to skip the confirmation prompt in scripts

Troubleshooting

PR not entering the queue:

  • Make sure the PR was queued: post @mergifyio queue and confirm Mergify reacted with 👍 on the comment
  • Check that the PR's merge conditions are met: mergify queue show <PR_NUMBER> -v
  • Look at the conditions section for unmet requirements
  • Do not assume the queue command never landed: queue show tells a PR that was queued and dequeued apart from one that never entered

PR stuck in queue:

  • Check CI state: mergify queue show <PR_NUMBER>
  • If checks are failing, -v names them; for the job logs, read the Failing checks: links in the # Merge Queue Status comment or the Mergify Merge Queue check-run summary
  • If the queue is paused, check who paused it: mergify queue status

PR disappeared from the queue:

  • mergify queue show <PR_NUMBER> — it says whether the PR was dequeued, merged by the queue, or never queued, and prints the dequeue code, the reason, and the failing checks' URLs
  • Never assume "not in the merge queue" means "never queued": read the headline (or dequeued under --json) before telling anyone to requeue
  • Then act per the reason table

Queue moving slowly:

  • Check for failing batches that trigger bisection: mergify queue status
  • Bisecting batches test PRs individually, which is slower than batch merging