View as Markdown

Keep Merge Queue Pull Requests Out of Your Way

Why the merge queue opens draft pull requests, when it checks in place instead, and how to filter queue pull requests out of searches, notifications, webhooks and CI.


To test what it is about to merge, the merge queue often opens a temporary draft pull request on a branch of its own: a queue pull request, called a batch PR on other pages. Queue pull requests show up in your pull request list, in webhooks, in deploy previews and in any tool that reacts to new pull requests. This page explains when and why the queue opens them, how to configure a queue that does without, and how to filter them out everywhere else.

Why the Queue Opens Draft Pull Requests

Section titled Why the Queue Opens Draft Pull Requests

The queue has to run your CI on the exact code it is about to merge. Often that code is not any single pull request’s branch:

  • Batches. With batch_size above 1, the queue tests several pull requests together, so it builds a branch holding all of them on top of the base branch.

  • Parallel checks. With max_parallel_checks above 1, the queue tests the next pull requests speculatively, on top of the ones still being checked ahead of them. Each of those predicted states needs its own branch.

  • Bisection. When a batch fails, the queue splits it to find the culprit, and each half is tested on a new branch.

  • Retries. With max_checks_retries above 0, every retry recreates the draft to get a fresh CI run.

  • Two-step CI. With merge_conditions that differ from queue_conditions, the merge conditions are evaluated on the draft, so the heavier suite runs there and not on the original pull request.

  • Parallel and isolated modes. In parallel and isolated modes, the queue tests several groups of pull requests side by side, each on its own branch.

The draft pull request is how your CI learns about that branch and how its results come back to the queue. Mergify deletes the branch, and with it the draft, once the batch is done.

When the Queue Checks in Place

Section titled When the Queue Checks in Place

When none of the above applies, the queue skips the draft. It updates the original pull request with the latest base branch, runs CI on the pull request itself, and merges it. A queue rule checks in place only when all of these hold:

  • merge_conditions is empty, or lists the same conditions as queue_conditions (no two-step CI).

  • batch_size is 1. With a {min, max} range, max is 1.

  • max_checks_retries is 0.

  • merge_queue.max_parallel_checks is 1.

  • merge_queue.mode is serial.

batch_size, max_checks_retries and mode default to those values, but max_parallel_checks defaults to 5. A queue that has never touched it opens draft pull requests, so the smallest configuration that checks in place sets it explicitly:

merge_queue:
max_parallel_checks: 1
queue_rules:
- name: default
queue_conditions:
- check-success = ci

The conditions are the same list as in In-place checks (no batch PRs), which also covers update_bot_account and pull requests coming from forks.

Checking in place gives up what the drafts were buying. The queue tests one pull request at a time, so it merges at most one pull request per CI run, and every queued pull request gets an update from the base branch pushed to it. See The Trade-offs to weigh that against your merge rate. If you need batches or parallel checks, keep the drafts and filter them with the recipes below.

Recognize a Queue Pull Request

Section titled Recognize a Queue Pull Request

A queue pull request carries four markers:

  • Head branch: the queue_branch_prefix followed by 10 hexadecimal characters, for example mergify/merge-queue/a76b1f3d25.

  • Author: mergify[bot], written app/mergify in GitHub search, unless the queue rule sets draft_bot_account.

  • State: draft.

  • Title: starts with merge queue: .

Match on the head branch. It holds for every queue pull request, whoever opens it and whatever state it is in, while the others have exceptions: a queue rule with draft_bot_account opens drafts under that user’s name, the merge-batch method marks the draft ready for review before merging it, and a draft state also matches the drafts your own developers open.

The prefix defaults to mergify/merge-queue/ and is set per queue rule. If you change it, change your filters to match. See How Leftover Branches Are Matched for the exact shape of the name.

Add -author:app/mergify to a pull request search to hide queue pull requests:

is:pr is:open -author:app/mergify

If a queue rule sets draft_bot_account, filter on the branch instead. head: matches branch names starting with the value:

is:pr is:open -head:mergify/merge-queue/

Save the search as a bookmark, or paste it into the filter box of the pull request list.

A service that receives GitHub’s pull_request webhook can drop queue pull requests by testing the head branch in the payload. In Python:

QUEUE_BRANCH_PREFIX = "mergify/merge-queue/"
def is_queue_pull_request(payload: dict) -> bool:
return payload["pull_request"]["head"]["ref"].startswith(QUEUE_BRANCH_PREFIX)

The same test fits any automation that posts to Slack or another chat tool, whether a workflow or a bot of your own. To also drop the drafts your developers open, test payload["pull_request"]["draft"] as well, but keep the branch test: a draft check alone misses the queue pull request once merge-batch marks it ready.

The GitHub app for Slack filters pull requests by label only, so it cannot exclude queue pull requests by branch or author. Route those notifications through a filter of your own if they are too noisy.

Skip a job on queue pull requests with an if: condition on the head branch. This suits jobs that do not gate the merge, such as deploy previews or labelers:

on:
pull_request:
jobs:
preview:
if: ${{ !startsWith(github.head_ref, 'mergify/merge-queue/') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make deploy-preview

The queue also pushes its branch, so a workflow triggered on push runs for it too. On push, filter with branches-ignore, which matches the pushed branch:

on:
push:
branches-ignore:
- "mergify/merge-queue/**"

To do the opposite and run a job only in the queue, see Two-Step CI.

CircleCI builds the queue branch when it is pushed. Ignore it in a workflow’s branch filter:

workflows:
preview:
jobs:
- deploy-preview:
filters:
branches:
ignore: /^mergify\/merge-queue\/.*/

Skip a step on queue branches with a conditional:

steps:
- label: ":rocket: Deploy preview"
command: make deploy-preview
if: build.branch !~ /^mergify\/merge-queue\//

A step-level branches: "!mergify/merge-queue/*" filter does the same. The pipeline-wide branch filter in the pipeline settings skips every step, including the ones your merge_conditions require, so keep it for pipelines that never gate a merge.

In a multibranch pipeline, the queue branch can be built both as a branch and as a pull request, where BRANCH_NAME becomes PR-123. Cover both with a when block:

stage('Deploy preview') {
when {
not {
anyOf {
branch 'mergify/merge-queue/*'
changeRequest branch: 'mergify/merge-queue/*', comparator: 'GLOB'
}
}
}
steps {
sh 'make deploy-preview'
}
}

Turn off Vercel deployments for queue branches in vercel.json with git.deploymentEnabled:

{
"git": {
"deploymentEnabled": {
"mergify/merge-queue/*": false
}
}
}

Vercel then skips the preview deployment for every queue branch. With a custom queue_branch_prefix, use that prefix followed by *.

Was this page helpful?