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:
@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
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.
Under --json, a PR that is not currently queued carries queued: false plus a dequeued discriminator:
dequeued: true— left without merging; the raw leave event is underqueue_leavedequeued: false— merged by the queue, or never queued (queue_leavetells the two apart:nullmeans never queued)dequeued: null— the lookup itself failed, andqueue_leave_errorsays 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:
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:
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.
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_codesays 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_fromis required in practice. The window defaults to the last 24 hours. A dequeue from last week returnssize: 0with no error, which reads exactly like "never queued". Always passreceived_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:
Titles map to state without any parsing:
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.
--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:
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.
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 aCHECKS_TIMEOUTbefore 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
Pausing and Unpausing
Pause the queue to temporarily halt all merges (e.g., during incidents or deployments):
- 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-sureto skip the confirmation prompt in scripts
Troubleshooting
PR not entering the queue:
- Make sure the PR was queued: post
@mergifyio queueand 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 showtells 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,
-vnames them; for the job logs, read theFailing checks:links in the# Merge Queue Statuscomment or theMergify Merge Queuecheck-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
dequeuedunder--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


