Mergify
Mergify v1.0.0
Publisher description
From the marketplace listing
Mergify serializes merges through a queue that runs CI on temporary merge commits, so a pull request that only breaks in combination with another one gets caught before it reaches your default branch. These skills drive the Mergify CLI so an agent can work that queue with you. Ask why a pull request left the queue and get the engine's own dequeue reason, the checks that failed, and a link straight to the job log. Read the activity log for one pull request or for the whole repository. Validate a .mergify.yml against the schema before you commit it, or simulate what your rules would do on a real pull request. Set up merge protections such as dependencies between pull requests or scheduled freezes for a release window. Turn a branch of commits into a stack of one pull request per commit and keep them rebased. Six skills ship in the bundle: mergify-merge-queue, mergify-events, mergify-config, mergify-merge-protections, mergify-ci, and mergify-stack. It runs on the Mergify CLI, a single static binary with no runtime to install, so it needs a local execution environment: Codex, or ChatGPT desktop with local execution. You need a GitHub repository with Mergify enabled. Install the CLI with 'brew install mergifyio/tap/mergify-cli', sign in once with 'mergify auth login' or set MERGIFY_TOKEN, then ask for what you want in plain language.
Language: English · Automatically detected from descriptions.
Files & skills
File archives
Skill instructions
mergify-ci19.4 KB
---
name: mergify-ci
description: Use Mergify CI commands to upload JUnit test results, detect git references, manage CI scopes, and retrieve merge queue metadata. ALWAYS use this skill when working with CI pipelines, test result uploads, quarantine, scopes detection, or merge queue CI context. Triggers on CI, JUnit, test results, quarantine, scopes, git refs, CI insights.
---
# Mergify CI Commands
## Overview
The `mergify ci` command group provides tools for CI pipelines: uploading JUnit test results to Mergify CI Insights, detecting git references for diff-based operations, managing CI scopes for selective testing, and retrieving merge queue batch metadata.
## Commands
```bash
mergify ci junit-process FILES... # Upload JUnit XML + evaluate quarantine (primary command)
mergify ci junit-upload FILES... # (Deprecated) Use junit-process instead
mergify ci git-refs # Detect base/head git references for the current PR
mergify ci scopes --config PATH # Detect scopes impacted by changed files
mergify ci scopes-send -s SCOPE # Report scopes against the pull request's head commit
mergify ci queue-info # Output the current build's merge queue batch metadata (from the git note)
mergify tests show NAME... # Look up tests by name and print health, ratios, last failure
mergify tests quarantines add NAME # Add a test to the CI Insights quarantine
mergify tests quarantines remove NAME # Remove a test from the CI Insights quarantine
mergify tests quarantines get NAME # Print a single quarantine by test name or id
mergify tests quarantines list # List the tests currently in the CI Insights quarantine
```
## Authentication
Two classes of Mergify application key, both minted in the dashboard:
| Key | Reaches |
| --- | --- |
| `ci` | What a CI job does: trace upload, `scopes-send`, quarantine *evaluation* (`junit-process`) and the quarantine *list*. |
| `admin` | Everything a `ci` key reaches, plus reading test health and mutating the quarantine. |
Every endpoint below that refuses a `ci` key accepts a GitHub PAT instead.
`GITHUB_TOKEN` inside GitHub Actions is *not* a PAT — it is the ephemeral
installation token — so do not reach for it to clear one of these `403`s.
The split follows the endpoint each command calls: reads of test health and
writes to the quarantine are a person inspecting or overriding a repository,
not something a pipeline does, so they are outside what a `ci` key carries.
Per command:
| Command | `ci` key |
| --- | --- |
| `ci junit-process`, `ci junit-upload`, `ci scopes-send` | yes |
| `tests quarantines list`, `tests quarantines get` | yes — both read the quarantine list |
| `tests show` | **no** — `403` |
| `tests quarantines add`, `tests quarantines remove` | **no** — `403` |
`ci git-refs`, `ci scopes` and `ci queue-info` are evaluated locally and need
no token at all.
## JUnit Processing (`junit-process`)
The primary CI command. Parses JUnit XML reports, checks quarantine status for failing tests, uploads results to Mergify CI Insights, and determines the final CI exit code.
```bash
mergify ci junit-process \
--token "$MERGIFY_TOKEN" \
--repository owner/repo \
--tests-target-branch main \
path/to/junit-results.xml
```
`FILES` can be individual paths or quoted glob patterns (e.g. `'reports/**/*.xml'`). Always quote the pattern so Mergify expands it rather than the shell — this is the recommended approach for large, sharded test suites.
**Key options:**
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- CI Insights application key
- `--repository` / `-r` -- Repository full name (auto-detected in GitHub Actions)
- `--tests-target-branch` / `-ttb` -- Branch used for quarantine evaluation. Auto-detected per CI provider: GitHub Actions (`GITHUB_BASE_REF` → `GITHUB_HEAD_REF` → `GITHUB_REF_NAME` → `GITHUB_REF`), Buildkite (`BUILDKITE_PULL_REQUEST_BASE_BRANCH` → `BUILDKITE_BRANCH`), CircleCI (`CIRCLE_BRANCH`), Jenkins (`CHANGE_TARGET` → `GIT_BRANCH`).
- `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- Mergify API URL (default: `https://api.mergify.com`)
- `--test-framework` -- Test framework name (optional metadata)
- `--test-language` -- Test language (optional metadata)
- `--test-exit-code` / `-e` (env: `MERGIFY_TEST_EXIT_CODE`) -- Exit code of the test runner, used to detect silent failures where the runner crashed but the JUnit report appears clean
**Behavior:**
1. Parses JUnit XML files into test spans
2. Checks quarantine status for failing tests against the Mergify API
3. Uploads all test spans to Mergify CI Insights
4. Prints a summary: tests run, failures, quarantined vs blocking
5. Exits with code 0 if all failures are quarantined, code 1 if any are blocking
6. If `--test-exit-code` is non-zero but no test failures are found, exits with code 1 (silent failure detection)
7. Upload failures never affect the exit code (Mergify-side trouble must not break CI), but they are surfaced: on GitHub Actions the command emits an `::error::` annotation when the upload is rejected (HTTP 4xx except 408/429, e.g. a token without CI Insights access) or a `::warning::` annotation for transient errors (5xx, 408, 429, network), and appends `test_results_upload=success|rejected|failed` to `$GITHUB_OUTPUT` so workflows can detect dead ingest programmatically
**GitHub Actions example:**
```yaml
- name: Run tests
id: tests
run: pytest --junitxml=results.xml || echo "exit_code=$?" >> "$GITHUB_OUTPUT"
- name: Process test results
if: always()
run: |
mergify ci junit-process \
--test-exit-code ${{ steps.tests.outputs.exit_code || 0 }} \
results.xml
env:
MERGIFY_TOKEN: ${{ secrets.MERGIFY_TOKEN }}
```
## Git References (`git-refs`)
Detects the base and head git references for the current pull request context. Writes results to `GITHUB_OUTPUT` when running in GitHub Actions, and to Buildkite meta-data (`mergify-ci.base`, `mergify-ci.head`, `mergify-ci.source`) when running in Buildkite.
```bash
mergify ci git-refs
# Output:
# Base: abc1234
# Head: def5678
```
**Output formats (`--format`):**
- `text` (default) — human-readable `Base:` / `Head:` lines
- `shell` — `MERGIFY_GIT_REFS_{BASE,HEAD,SOURCE}=...` lines suitable for `eval`, with POSIX-safe shell quoting. When base can't be detected, `MERGIFY_GIT_REFS_BASE=''`.
- `json` — single-line JSON object with `base`, `head`, `source` keys. `base` may be `null` when it can't be detected (e.g., `workflow_dispatch` events); use `jq -r '.base // ""'` to coalesce to empty string.
```bash
# Consume values in a shell script without parsing:
eval "$(mergify ci git-refs --format=shell)"
nx show projects --affected \
--base="$MERGIFY_GIT_REFS_BASE" \
--head="$MERGIFY_GIT_REFS_HEAD"
# Or with jq:
BASE=$(mergify ci git-refs --format=json | jq -r '.base // ""')
```
Sources detected (in priority order): merge queue context, GitHub pull request event, GitHub push event, fallback to last commit.
## Scopes (`scopes`)
Detects which CI scopes are impacted by changed files, based on a Mergify configuration file. Used for selective/targeted CI -- only run tests for scopes that have changed files.
```bash
# Detect scopes from changed files
mergify ci scopes --config .mergify.yml
# Detect scopes with explicit base/head
mergify ci scopes --config .mergify.yml --base origin/main --head HEAD
# Write detected scopes to a file
mergify ci scopes --config .mergify.yml --write scopes.json
```
**Key options:**
- `--config` (env: `MERGIFY_CONFIG_PATH`) -- Path to the Mergify YAML config file (auto-detected)
- `--base` -- Base git reference (auto-detected)
- `--head` -- Head git reference (default: HEAD)
- `--write` / `-w` -- Write detected scopes to a JSON file
The config file defines scopes with file patterns. When files change between base and head, matching scopes are identified and written to `GITHUB_OUTPUT` (on GitHub Actions) or to Buildkite meta-data under `mergify-ci.scopes` (on Buildkite), as a JSON map of scope names to `"true"`/`"false"`.
A renamed file counts against **both** of its paths, same as the engine: `git mv critical/guard.txt ignored/guard.txt` touches `critical` and `ignored`.
## Scopes Send (`scopes-send`)
Sends scopes tied to a pull request to the Mergify API. Used when scopes are determined manually or from a file rather than auto-detected.
The report is addressed by the pull request's **head SHA**, so it says which revision it was computed for and a result computed for an older head cannot be taken for the current one. The head is detected from the CI environment (GitHub Actions event payload, or `BUILDKITE_COMMIT`); pass `--head-sha` to name it explicitly. When no head SHA can be resolved -- or the Mergify deployment predates the commit endpoint -- the command falls back to reporting against the pull request number alone, which is what it always did.
```bash
# Send specific scopes
mergify ci scopes-send -s frontend -s backend -p 123
# Send scopes from a JSON file (produced by `mergify ci scopes --write`)
mergify ci scopes-send --scopes-json scopes.json -p 123
# Send scopes from a plain-text file (one scope per line)
mergify ci scopes-send --scopes-file scopes.txt -p 123
# Declare the PR impacts every scope (merge-queue barrier),
# e.g. a build-system or CI-workflow change
mergify ci scopes-send -s build-system --all -p 123
# Name the revision explicitly (the pull request head, not the
# revision a `pull_request` job checked out)
mergify ci scopes-send -s frontend -p 123 --head-sha "$PR_HEAD_SHA"
```
**Key options:**
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify key
- `--repository` / `-r` -- Repository full name (auto-detected)
- `--pull-request` / `-p` -- Pull request number (auto-detected in GitHub Actions)
- `--scope` / `-s` -- Scope name (repeatable)
- `--scopes-json` -- JSON file containing scopes (output of `mergify ci scopes --write`)
- `--scopes-file` -- Plain-text file with one scope per line
- `--head-sha` -- Head SHA the scopes were computed for, 40 hexadecimal characters (auto-detected from the CI environment)
- `--all` -- Declare the pull request impacts every scope. The merge queue treats it as a barrier: never batched or run in parallel with other pull requests. The concrete scopes are still sent alongside the flag.
## Tests Show (`tests show`)
Looks up tests by name on the repository's default branch and prints their
health, success/failure ratios, and last failure context. The search is a
batch API: pass one or more names (globs supported) and one block per match
is rendered. It is read-only: it exits `0` once it has rendered the matches,
whatever their health. Gate on health by consuming `--json`, not the exit code.
Needs an `admin` key or a GitHub PAT — a `ci` key gets a `403`. See
[Authentication](#authentication).
```bash
# Single test.
mergify tests show -r owner/repo \
'ApplicationKeys.spec.ts.Permissions › Should not see keys table if not admin'
# Batch with glob, narrowed to one pipeline, JSON for jq.
mergify tests show -r owner/repo \
--pipeline-name e2e --json \
'*test_login*' '*test_logout*' \
| jq '.tests[] | {test_name, health_status}'
```
**Key options:**
- `--repository` / `-r` -- Repository full name (`owner/repo`); auto-detected from the CI environment or the local git remote when omitted.
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify credential. Falls back to the credential `mergify auth login` stored, then to `GITHUB_TOKEN` / `gh auth token`, both deprecated for the Mergify API.
- `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- API base URL.
- `--pipeline-name`, `--pipeline-name-exclude` -- Restrict / exclude by pipeline.
- `--job-name`, `--job-name-exclude` -- Restrict / exclude by job.
- `--per-page` -- Cap the search result count (1–100, server default 10).
- `--json` -- Emit a single JSON document `{"tests": [...]}` to stdout.
**Exit codes:**
- `0` -- Tests rendered, or no match at all. Health does **not** affect it.
- `6` -- Mergify API error, including the `403` a `ci` key gets here.
## Tests Quarantines Add (`tests quarantines add`)
Adds a test to the repository's CI Insights quarantine, so its failures stop
blocking the CI verdict. Takes a single fully qualified test name; a `--reason`
is required.
Needs an `admin` key or a GitHub PAT — a `ci` key gets a `403`. See
[Authentication](#authentication).
```bash
# Quarantine on all branches.
mergify tests quarantines add -r owner/repo \
--reason 'flaky — tracked in MRGFY-1234' \
'test_login'
# Scope the quarantine to one branch (or branch pattern), JSON output.
mergify tests quarantines add -r owner/repo \
--reason 'broken on release branch' --branch 'release/*' --json \
'test_logout'
```
**Key options:**
- `--repository` / `-r` -- Repository full name (`owner/repo`); auto-detected from the CI environment or the local git remote when omitted.
- `--reason` -- Reason recorded for the quarantine; required.
- `--branch` / `-b` -- Branch name or pattern to scope to. Omit for all branches.
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify credential. Falls back to the credential `mergify auth login` stored, then to `GITHUB_TOKEN` / `gh auth token`, both deprecated for the Mergify API.
- `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- API base URL.
- `--json` -- Emit `{"id", "test_name", "reason", "branch"}` to stdout.
**Exit codes:**
- `0` -- Test quarantined.
- `6` -- Mergify API error (e.g. the test is already quarantined).
## Tests Quarantines Remove (`tests quarantines remove`)
Removes a test from the quarantine. Accepts either the fully qualified test
name (resolved to its quarantine id via the list endpoint) or the quarantine
id directly (as printed by `tests quarantines add`). A UUID-shaped argument
is treated as the id and deleted without a lookup.
Needs an `admin` key or a GitHub PAT — a `ci` key gets a `403`. See
[Authentication](#authentication).
```bash
# By test name.
mergify tests quarantines remove -r owner/repo 'test_login'
# By quarantine id (the value `tests quarantines add` printed).
mergify tests quarantines remove -r owner/repo 12345678-1234-5678-1234-567812345678
```
**Key options:**
- `--repository` / `-r` -- Repository full name (`owner/repo`); auto-detected from the CI environment or the local git remote when omitted.
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify credential. Falls back to the credential `mergify auth login` stored, then to `GITHUB_TOKEN` / `gh auth token`, both deprecated for the Mergify API.
- `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- API base URL.
- `--json` -- Emit `{"id", "test_name"}` to stdout (`test_name` is null when
addressed by id).
**Exit codes:**
- `0` -- Test unquarantined.
- `6` -- Mergify API error (e.g. the test is not quarantined).
## Tests Quarantines Get (`tests quarantines get`)
Prints a single quarantine, addressed by the fully qualified test name or the
quarantine id (a UUID-shaped argument is matched against the id). The output
mirrors one record of `tests quarantines list`.
```bash
# By test name.
mergify tests quarantines get -r owner/repo 'test_login'
# By quarantine id, JSON output.
mergify tests quarantines get -r owner/repo --json \
12345678-1234-5678-1234-567812345678
```
**Key options:**
- `--repository` / `-r` -- Repository full name (`owner/repo`); auto-detected from the CI environment or the local git remote when omitted.
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify credential. Falls back to the credential `mergify auth login` stored, then to `GITHUB_TOKEN` / `gh auth token`, both deprecated for the Mergify API.
- `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- API base URL.
- `--json` -- Emit the record (`id`, `test_name`, `reason`, `branch`, `created_at`,
`source`, `is_recovered`) to stdout.
**Exit codes:**
- `0` -- Quarantine found and printed.
- `6` -- Mergify API error (e.g. no matching quarantine).
## Tests Quarantines List (`tests quarantines list`)
Lists every test currently in the repository's CI Insights quarantine. Takes no
test argument -- it prints the whole quarantine. Human output is one indented
block per record -- the test name on its own line (never wrapped mid-name),
then its id (the value `delete` accepts), branch, source, recovered, and
reason. `--json` emits the full records.
```bash
# Human output (one block per record).
mergify tests quarantines list -r owner/repo
# JSON for jq -- e.g. names of quarantines an auto-recover run flagged.
mergify tests quarantines list -r owner/repo --json \
| jq -r '.quarantined_tests[] | select(.is_recovered) | .test_name'
```
A null `branch` renders as `*` (the quarantine applies to all branches).
`source` is `manual` (added by a user) or `auto` (added by flaky detection).
`is_recovered` flags quarantines whose recent runs suggest they can be removed.
**Key options:**
- `--repository` / `-r` -- Repository full name (`owner/repo`); auto-detected from the CI environment or the local git remote when omitted.
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify credential. Falls back to the credential `mergify auth login` stored, then to `GITHUB_TOKEN` / `gh auth token`, both deprecated for the Mergify API.
- `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- API base URL.
- `--json` -- Emit `{"quarantined_tests": [...]}` to stdout, each record carrying
`id`, `test_name`, `reason`, `branch`, `created_at`, `source`, `is_recovered`.
**Exit codes:**
- `0` -- Always, including an empty quarantine ("No quarantined tests found.").
## Queue Info (`queue-info`)
Outputs the current build's merge queue batch metadata as JSON. Reads the `refs/notes/mergify/<branch>` git note Mergify writes on the merge queue branch head for the current `HEAD`; exits `INVALID_STATE` (7) when no note is found (i.e. not a merge queue batch build). Takes no arguments and needs no GitHub token.
The note's full payload is emitted verbatim (every field Mergify wrote, e.g. `checking_base_sha`, `pull_requests` with per-PR `scopes`, `previous_failed_batches`, top-level `scopes`). New fields appear automatically without a CLI update, so don't assume a fixed schema.
```bash
# In CI, on a merge queue batch build. Works in any CI (GitHub Actions,
# GitLab, CircleCI, Jenkins, ...) with plain git. When GITHUB_OUTPUT is
# set (GitHub Actions runner) it also writes the metadata there.
mergify ci queue-info
```
Mergify attaches the batch metadata as a git note to the MQ branch head commit, so reading it needs only git, no PR body and no GitHub token. The command runs `git fetch origin "refs/notes/mergify/*"` itself (notes aren't fetched by default), then returns the note attached to `HEAD`. Requirements: the `origin` remote is reachable and the checkout is at the MQ branch head commit, which is the normal state inside a merge-queue CI run. A detached HEAD is fine.
This command is useful in CI workflows that need to know whether the current run is part of a merge queue batch and what other PRs are in the batch.
## Common Patterns
### Full CI pipeline with quarantine
```yaml
jobs:
test:
steps:
- uses: actions/checkout@v4
- name: Run tests
run: pytest --junitxml=results.xml
- name: Upload and evaluate
if: always()
run: mergify ci junit-process results.xml
env:
MERGIFY_TOKEN: ${{ secrets.MERGIFY_TOKEN }}
```
### Selective testing with scopes
```yaml
jobs:
detect:
outputs:
scopes: ${{ steps.scopes.outputs.scopes }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: scopes
run: mergify ci scopes --config .mergify.yml
backend:
needs: detect
if: fromJSON(needs.detect.outputs.scopes).backend == 'true'
steps:
- run: pytest backend/
```
mergify-config5.41 KB
---
name: mergify-config
description: Use Mergify config commands to validate configuration files, simulate Mergify actions, and write Mergify configuration. ALWAYS use this skill when validating, writing, editing, or simulating Mergify config. Triggers on config validate, config simulate, mergify configuration, mergify.yml, .mergify.yml, merge queue config, workflow rules, conditions.
---
# Mergify Configuration Management
## Overview
The `mergify config` command group provides tools for validating Mergify configuration files against the official schema and simulating what Mergify would do on a specific pull request using a local configuration file.
## Commands
```bash
mergify config validate # Validate the configuration file
mergify config simulate PULL_REQUEST_URL # Simulate actions on a PR
```
## Configuration File Detection
Mergify CLI auto-detects the configuration file from standard locations:
- `.mergify.yml`
- `.mergify/config.yml`
- `.github/mergify.yml`
Override with `--config-file` / `-f`:
```bash
mergify config -f path/to/config.yml validate
```
## Validating Configuration (`validate`)
Validates the Mergify configuration file against the official JSON schema fetched from `https://docs.mergify.com/mergify-configuration-schema.json`.
```bash
# Validate auto-detected config file
mergify config validate
# Validate a specific file
mergify config -f .mergify.yml validate
```
**Output:**
- If valid: prints a success message and exits with code 0
- If invalid: prints the number of errors and each error's path and message, then exits with code 1
**Use in CI:**
```yaml
- name: Validate Mergify config
run: mergify config validate
```
## Simulating Actions (`simulate`)
Simulates what Mergify would do on a specific pull request using the local configuration file. This lets you test configuration changes before committing them.
```bash
mergify config simulate https://github.com/owner/repo/pull/123
```
**Required arguments:**
- `PULL_REQUEST_URL` -- Full GitHub URL of the pull request to simulate against
**Options:**
- `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify credential. Falls back to the credential `mergify auth login` stored, then to `GITHUB_TOKEN` / `gh auth token`, both deprecated for the Mergify API.
- `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- Mergify API URL (default: `https://api.mergify.com`)
**Output:** Shows a title and detailed Markdown summary of what actions Mergify would take on the PR with the local configuration.
## Writing and Editing Configuration
When helping a user write, edit, or understand a Mergify configuration file,
**always fetch the relevant documentation pages first**. Do not rely on
memorized knowledge -- the configuration format, available actions, and
conditions syntax evolve over time.
### Documentation index
Fetch `https://docs.mergify.com/llms.txt` to get the full documentation index
with all available pages and their descriptions.
### Key reference pages
Fetch the pages relevant to the user's request before writing any configuration:
| Topic | URL |
|-------|-----|
| File format and structure | `https://docs.mergify.com/configuration/file-format` |
| Conditions syntax and attributes | `https://docs.mergify.com/configuration/conditions` |
| Data types (duration, templates) | `https://docs.mergify.com/configuration/data-types` |
| Configuration sharing and reuse | `https://docs.mergify.com/configuration/sharing` |
| Workflow automation overview | `https://docs.mergify.com/workflow` |
| All available actions | `https://docs.mergify.com/workflow/actions` |
| Writing your first rule | `https://docs.mergify.com/workflow/writing-your-first-rule` |
| Merge queue rules | `https://docs.mergify.com/merge-queue/rules` |
| Merge queue setup | `https://docs.mergify.com/merge-queue/setup` |
| Queue priority rules | `https://docs.mergify.com/merge-queue/priority` |
| Merge protections | `https://docs.mergify.com/merge-protections/setup` |
| Custom protection rules | `https://docs.mergify.com/merge-protections/custom-rules` |
| Merge protection examples | `https://docs.mergify.com/merge-protections/examples` |
| Commands and restrictions | `https://docs.mergify.com/commands` |
| JSON Schema | `https://docs.mergify.com/mergify-configuration-schema.json` |
For individual actions (assign, backport, close, comment, copy, dismiss_reviews,
edit, github_actions, label, merge, queue, rebase, request_reviews, review,
squash, update), fetch the specific action page at
`https://docs.mergify.com/workflow/actions/<action_name>`.
### Workflow
1. **Read the user's existing config** (if any) to understand current state
2. **Fetch the relevant doc pages** for the features the user needs
3. **Write or update the configuration** based on the documentation
4. **Validate**: run `mergify config validate` to check for errors
5. **Simulate** (optional): run `mergify config simulate <PR_URL>` against a
real PR to verify the rules behave as expected
## Common Patterns
### Test config changes before pushing
```bash
# Edit your .mergify.yml locally, then:
mergify config validate
mergify config simulate https://github.com/myorg/myrepo/pull/42
# If both look good, commit and push
```
### CI validation gate
```yaml
jobs:
validate-mergify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mergify-cli
run: pip install mergify-cli
- name: Validate Mergify config
run: mergify config validate
```
mergify-events4.02 KB
---
name: mergify-events
description: Use `mergify events` to browse the Mergify activity log — every event Mergify recorded for a repository or one pull request (queue enters/leaves, merges, commands, CI Insights, freezes), as a human timeline or JSON. ALWAYS use this skill when investigating what Mergify did to a PR or repository and when, reconstructing a pull request's merge-queue lifecycle, auditing Mergify actions, or filtering events by type. Triggers on activity log, event log, events, what did Mergify do, queue lifecycle, queue history, event timeline, event_type, action.queue.
---
# Mergify Events
## Overview
`mergify events` lists the repository's Mergify activity log as a timeline — the ~45 event types the engine records: the `action.queue.*` lifecycle, workflow actions (`action.merge`, `action.rebase`, `action.label`, …), user commands (`command.queue`, `command.dequeue`), `ci_insights.*`, queue pauses, and scheduled freezes. One command over filters; there is deliberately no command per event type.
```bash
mergify events # whole repo, last 24h
mergify events --pr 1740 # one PR's events, last 24h
mergify events --pr 1740 --since 7d # wider window (s/m/h/d/w, max 90d)
mergify events --type action.queue.leave --type command.queue # filter, repeatable
mergify events --pr 1740 --json # raw events, newest first
mergify events --limit 20 # newest 20 only (the header says so)
```
## The window rule (the one thing to get right)
Every result covers an **explicit time window**, stated in the header and in the empty-case message:
```
PR #1740 · 6 events · 2026-07-29 21:00 → 2026-07-30 21:00 UTC
```
- Default window: the **last 24 hours**. A PR dequeued last week shows **nothing** in the default window — that is "nothing in the last 24h", never "no history".
- Retention is **90 days**; `--since 90d` is the widest useful window. Anything wider is rejected up front with the fix in the message.
- An empty result names the window: `No events for PR #1740 between <from> and <to> UTC.` If you did not search the full retention yet, widen with `--since 90d` before concluding anything.
## Reading the timeline
Oldest first (it reads down the page), with a summary per event where the metadata carries one:
```
2026-07-30
14:02 action.queue.enter default
14:31 action.queue.checks_start default · draft PR #1801
15:04 action.queue.checks_end CHECKS_RETRIED
15:04 action.queue.leave CHECKS_FAILED
15:12 command.queue @jd
```
Caveat that prevents a real mistake: an abort code on **`action.queue.checks_end`** (`PR_AHEAD_DEQUEUED`, `MERGE_QUEUE_RESET`, `CHECKS_RETRIED`, …) means the *checks* were interrupted while the PR **stayed queued**. Only **`action.queue.leave`** means the PR left the queue — and its `merged: true` variant means it left by merging. Do not requeue a PR over a `checks_end` event.
## JSON contract
`--json` emits one document; `events` are the API's raw objects (unknown fields intact), **newest first**:
```json
{
"repository": "owner/repo",
"pull_request": 1740,
"received_from": "2026-07-29T21:00:00+00:00",
"received_to": "2026-07-30T21:00:00+00:00",
"size": 6,
"events": [ { "id": 123, "type": "action.queue.leave", "received_at": "…", "metadata": { "…": "…" } } ]
}
```
The window is echoed so an empty `events` is self-describing. Filter with `jq` on `.events[].type` and `.events[].metadata`.
## When to use something else
- **"Why was this PR dequeued?"** — `mergify queue show <PR>` (the `mergify-merge-queue` skill) is the dedicated answer: it renders the last leave event with the reason, the failing checks' job URLs, and the head-SHA staleness check. `mergify events` is for the *whole* trail or for non-queue events.
- **Raw API access** (CLI not installed, or a token refused the log): `GET /v1/repos/{owner}/{repo}/logs` — same data; always pass `received_from` (the API silently defaults to 1 day) and keep the span ≤ 93 days.
mergify-merge-protections7.48 KB
--- name: mergify-merge-protections description: Use Mergify merge protections to control when PRs merge — PR dependencies (Depends-On header), delayed merges (Merge-After header), and scheduled freezes (CLI). ALWAYS use this skill when managing merge freezes, deployment windows, temporarily blocking merges, setting up PR dependencies, blocking a PR on another PR, coordinating cross-repo merges, scheduling a merge for a specific time, or adding Depends-On or Merge-After headers to PRs. Triggers on freeze, scheduled freeze, merge freeze, deployment freeze, halt merges, depends on, dependency, block merge, merge after, merge later, schedule merge, delayed merge, cross-repo, merge protection. --- # Mergify Merge Protections Merge protections control when PRs are allowed to merge: | Protection | Scope | How | |------------|-------|-----| | **Depends-On** | Per-PR dependency chain | `Depends-On:` header in PR body | | **Merge-After** | Per-PR time gate | `Merge-After:` header in PR body | | **Scheduled Freezes** | Repository-wide or conditional | CLI commands (`mergify freeze`) | `Depends-On` and `Merge-After` are built-in — just add the header to the PR description (or commit message body when using `mergify stack push`) and Mergify enforces them automatically, no `.mergify.yml` configuration needed. Freezes are managed via CLI commands. ## Depends-On Block a PR from merging until one or more other PRs are merged first. ### Syntax Add one or more `Depends-On:` lines to the PR body: ``` Depends-On: #123 Depends-On: https://github.com/org/other-repo/pull/456 Depends-On: org/other-repo#789 ``` All three formats are supported — use `#NNN` for same-repo, full URL or `org/repo#NNN` for cross-repo. ### Rules - All referenced PRs must be in repositories with Mergify enabled - All referenced PRs must belong to the same GitHub organization - Circular dependencies and self-references are silently ignored - Multiple `Depends-On:` lines are allowed (one per line) ### With `mergify stack push` The stack tool **automatically** adds `Depends-On: #NNN` between consecutive PRs in a stack. For dependencies *outside* the stack (cross-repo or unrelated PRs), add the header manually to the **commit message body** — it will be copied to the PR description on push. ### When to suggest - Feature spanning multiple repos (e.g., API change + client update) - Schema migration must merge before application code - Shared library update must land before consumers ## Merge-After Postpone merging until a specified date and time. ### Syntax Add a `Merge-After:` line to the PR body: ``` Merge-After: 2025-09-01T09:00:00Z ``` ### Supported timestamp formats (ISO 8601) ``` Merge-After: 2025-09-01 # date only (midnight UTC) Merge-After: 2025-09-01T09:00:00 # no timezone (assumed UTC) Merge-After: 2025-09-01T09:00:00Z # explicit UTC Merge-After: 2025-09-01T09:00:00+02:00 # with UTC offset Merge-After: 2025-09-01T09:00:00[Europe/Paris] # with IANA timezone ``` If no timezone is specified, UTC is assumed. ### When to suggest - Coordinated release: multiple PRs should merge together at a specific time - Merge during a maintenance window or off-peak hours - Embargo: PR is ready but should not ship before a date ### Combining Depends-On and Merge-After Both headers can be used together on the same PR: ``` This PR updates the billing API to support the new pricing model. Depends-On: org/billing-service#42 Merge-After: 2025-06-15T10:00:00[US/Eastern] ``` The PR will not merge until PR #42 in `billing-service` is merged **and** the specified time has passed. ## Scheduled Freezes Scheduled freezes temporarily halt merging of pull requests matching specific conditions. Use them for deployment windows, incident response, maintenance periods, or any situation where merges should be paused. ## Commands ```bash mergify freeze list # List all scheduled freezes mergify freeze list --json # Machine-readable JSON output mergify freeze create OPTIONS # Create a new scheduled freeze mergify freeze update FREEZE_ID OPTIONS # Update an existing freeze mergify freeze delete FREEZE_ID # Delete a freeze ``` ## Authentication All commands require a Mergify credential. Run `mergify auth login` once, or: - `--token` / `-t` (env: `MERGIFY_TOKEN`) -- Mergify credential. Falls back to the credential `mergify auth login` stored, then to `GITHUB_TOKEN` / `gh auth token`, both deprecated for the Mergify API. - `--repository` / `-r` -- Repository full name (auto-detected from git remote) - `--api-url` / `-u` (env: `MERGIFY_API_URL`) -- Mergify API URL (default: `https://api.mergify.com`) ## Creating a Freeze ```bash # Emergency freeze (starts now, no end time) mergify freeze create \ --reason "Production incident - halting all merges" \ --timezone "US/Eastern" # Scheduled maintenance window mergify freeze create \ --reason "Weekend deployment freeze" \ --timezone "Europe/Paris" \ --start "2024-12-20T18:00:00" \ --end "2024-12-23T08:00:00" # Freeze with conditions (only freeze merges to main) mergify freeze create \ --reason "Release freeze for v2.0" \ --timezone "UTC" \ -c "base=main" # Freeze with exclusions (allow hotfix PRs through) mergify freeze create \ --reason "Code freeze" \ --timezone "UTC" \ -c "base=main" \ -e "label=hotfix" ``` **Required options:** - `--reason` -- Human-readable reason for the freeze - `--timezone` -- IANA timezone name (e.g., `Europe/Paris`, `US/Eastern`, `UTC`) **Optional options:** - `--start` -- Start time in ISO 8601 format (default: now) - `--end` -- End time in ISO 8601 format (default: no end, emergency freeze) - `--condition` / `-c` -- Matching condition (repeatable, e.g., `-c 'base=main'`) - `--exclude` / `-e` -- Exclude condition (repeatable, e.g., `-e 'label=hotfix'`) ## Listing Freezes ```bash # Table view mergify freeze list # JSON output for scripting mergify freeze list --json ``` The table shows: ID, reason, start/end times with timezone, matching conditions, and active/scheduled status. ## Updating a Freeze ```bash # Extend a freeze mergify freeze update FREEZE_ID --end "2024-12-24T08:00:00" # Change the reason mergify freeze update FREEZE_ID --reason "Extended: waiting for hotfix" # Set exclusions (replaces the full exclusion list, does not append) mergify freeze update FREEZE_ID -e "label=emergency" ``` The `FREEZE_ID` is the UUID shown in `mergify freeze list`. ## Deleting a Freeze ```bash # Delete a scheduled (not yet active) freeze mergify freeze delete FREEZE_ID # Delete an active freeze (reason required) mergify freeze delete FREEZE_ID --reason "Incident resolved" ``` If the freeze is currently active, a `--reason` for deletion is required. ## Common Patterns ### Emergency freeze during an incident ```bash # Stop all merges immediately mergify freeze create \ --reason "Incident #1234 - API outage" \ --timezone UTC # Once resolved, delete the freeze mergify freeze list --json # Get the freeze ID mergify freeze delete FREEZE_UUID --reason "Incident #1234 resolved" ``` ### Recurring deployment window Create the freeze before each deployment window and delete it after: ```bash mergify freeze create \ --reason "Deploy window" \ --timezone "US/Pacific" \ --start "2024-12-20T14:00:00" \ --end "2024-12-20T16:00:00" ``` ### Freeze with exceptions for critical fixes ```bash mergify freeze create \ --reason "Sprint freeze" \ --timezone UTC \ -c "base=main" \ -e "label=hotfix" \ -e "label=security" ```
mergify-merge-queue20.4 KB
---
name: mergify-merge-queue
description: Use Mergify merge queue to queue/dequeue PRs, to monitor and inspect the queue, and to diagnose a dequeued PR — whether it is queued, why it was dequeued, where its CI failure is, and what to do next. ALWAYS use this skill when queuing or dequeuing a PR, checking queue status, investigating PR merge state, finding out why a PR left the queue, pausing/unpausing the queue, or debugging merge failures. Triggers on queue a PR, requeue, dequeue, dequeued, why was my PR dequeued, dequeue reason, merge queue, queue status, queue pause, queue show, pause, unpause, frozen, bisecting, batch, CI checks, CHECKS_FAILED, PULL_REQUEST_UPDATED.
---
# 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](#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
```bash
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:
```bash
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:
```bash
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.
```bash
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'
```
```json
{
"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:
```bash
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.
```bash
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):
```bash
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](#dequeue-reasons-and-what-to-do-next)
**Queue moving slowly:**
- Check for failing batches that trigger bisection: `mergify queue status`
- Bisecting batches test PRs individually, which is slower than batch merging
mergify-stack17.1 KB
---
name: mergify-stack
description: Use Mergify stacks for git push, commit, branch, and PR creation. ALWAYS use this skill when pushing code, creating commits, creating branches, or creating PRs. Triggers on push, commit, branch, PR, pull request, stack, stacked, git, rebase, checkout, reorder, move, sync, amend, note, revision history.
---
# Mergify Stack Workflow
## Stack Philosophy
A branch is a stack. Keep stacks short and focused:
- A stack should only contain commits that **depend on each other**
- Rationale: longer stacks take longer to merge
**Proactive stack management:**
- If an existing stack can be split into independent stacks, offer to do so
- When asked to do something new: if it can be done on a separate branch, either do so or ask if in doubt
- Default to creating a new branch for unrelated changes
## Core Conventions
- **Push**: Use `mergify stack push` (never `git push`)
- **Fixes**: Use `git commit --amend` (never create new commits to fix issues)
- **Amend notes**: When amending a commit that already has a PR (i.e. has been pushed), attach a `mergify stack note` BEFORE `mergify stack push` to record *why* the commit was amended. The note appears in the PR's "Revision history" comment and JSON marker, so reviewers can see the reason without diffing.
- **Mid-stack fixes**: Stash any local changes first (`git stash -u`), then use `mergify stack edit <SHA-or-Change-Id-prefix>` to pause the rebase at the target commit. Amend it with `git commit --amend`, then `git rebase --continue`, then `mergify stack push`, then `git stash pop`. Non-interactive — never use `git rebase -i` for this. (Calling `mergify stack edit` with no argument falls back to a fully interactive `git rebase -i` and will hang in agent contexts — always pass a commit prefix.)
- **Reordering**: Stash any local changes first (`git stash -u`), then use `mergify stack reorder` (list all commits in desired order) or `mergify stack move` (move a single commit) instead of manual `git rebase -i` — non-interactive and avoids `GIT_SEQUENCE_EDITOR` quoting issues
- **Fixup**: Stash any local changes first (`git stash -u`), then use `mergify stack fixup <SHA>...` to fold a commit into its parent (drops the listed commit's message). Non-interactive — never use `git rebase -i` for this.
- **Squash**: Stash any local changes first (`git stash -u`), then use `mergify stack squash SRC... into TARGET [-m "msg"]` to combine multiple commits into one, with an optional custom message. Non-interactive — never use `git rebase -i` for this.
- **Reword**: Stash any local changes first (`git stash -u`), then use `mergify stack reword <SHA> -m "new message"` to change a commit's message in place. Non-interactive when `-m` is given — never use `git rebase -i` for this.
- **Drop**: Stash any local changes first (`git stash -u`), then use `mergify stack drop <SHA>...` to remove commits from the stack. Non-interactive — never use `git rebase -i` for this.
- **Commit titles**: Follow [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat:`, `fix:`, `docs:`)
- **PR title & body**: `mergify stack` copies the commit message title to the PR title and the commit message body to the PR body — so write commit messages as if they were PR descriptions. **Everything that should appear in the PR (ticket references, context, test plans) MUST go in the commit message.**
- **Ticket references**: Include ticket/issue references (e.g., `MRGFY-1234`, `Fixes #123`) in the commit message body, not added separately to the PR.
- **PR lifecycle is fully managed by `mergify stack`**: NEVER edit PR titles, bodies, or labels with `gh pr edit` or the GitHub MCP — they will be overwritten on the next push. NEVER close or merge PRs manually — `mergify stack` handles the entire PR lifecycle (creation, updates, and cleanup).
- **Draft PRs**: NEVER mark a PR as ready-for-review — all PRs stay as drafts. The user will manually move them out of draft after reviewing.
- **Each commit must pass CI independently**: Every commit in a stack becomes its own PR. Each PR runs CI separately, so every commit must be self-contained — it must compile, pass linters, and pass tests on its own without depending on later commits in the stack. When formatting or linting fixes are needed, they must be included in the commit that introduced the issue, not deferred to a later commit.
## Common Mistakes
| Wrong | Right | Why |
|-------|-------|-----|
| `git push` | `mergify stack push` | Git push bypasses stack management and breaks PR relationships |
| New commit to fix lint/typo | `git commit --amend` (HEAD) or `git commit --fixup <SHA>` + `git rebase --autosquash` (mid-stack) | Each commit = a PR; fix commits create unwanted extra PRs |
| `gh pr edit --title "..."` | Edit the commit message, then `mergify stack push` | PR title/body are overwritten from commit messages on every push |
| `gh pr merge` or `gh pr close` | PR lifecycle is fully managed — do nothing | PR lifecycle is fully managed by the stack tool |
| `git commit` on `main` | `mergify stack new <name>` first | `mergify stack push` will fail on the default branch |
| `git rebase -i` to fixup a commit | `mergify stack fixup <SHA>` | Non-interactive — works inside LLM/agent sessions; no editor spawned |
| `git rebase -i` to squash commits | `mergify stack squash A B into X [-m "..."]` | Non-interactive — works inside LLM/agent sessions; no editor spawned |
| `git rebase -i` to change a commit message | `mergify stack reword <SHA> -m "..."` | Non-interactive — works inside LLM/agent sessions; no editor spawned |
| `git rebase -i` to amend a mid-stack commit | `mergify stack edit <SHA-or-Change-Id-prefix>` then `git commit --amend` then `git rebase --continue` | Non-interactive — pauses the rebase at the target commit without spawning an editor |
| `git rebase -i` to drop a commit | `mergify stack drop <SHA>...` | Non-interactive — works inside LLM/agent sessions; no editor spawned |
| `GIT_SEQUENCE_EDITOR='sed -i ...' git rebase -i` (any variant) | One of `mergify stack {edit,fixup,squash,reorder,move}` | Hand-rolled sequence-editor scripts are brittle; there is already a non-interactive command for every common rewrite |
| Deferring lint fixes to a later commit | Include the fix in the commit that caused it | Each commit runs CI independently; later commits won't save earlier ones |
| Rebase/reorder/checkout/sync with dirty worktree | `git stash -u` first, then `git stash pop` after | Uncommitted changes are lost or cause conflicts during these operations |
| Amending a pushed commit with no explanation | `mergify stack note -m "why"` before `mergify stack push` | The reason is recorded in the PR's Revision history table and JSON marker, so reviewers don't need to diff to understand the change |
## Commands
```bash
mergify stack new NAME # Create a new stack/branch for new work
mergify stack push # Push and create/update PRs (also registers as a GitHub-native stack by default)
mergify stack push --no-github-native # ...without registering it as a GitHub-native stack
mergify stack checkout BRANCH # Checkout an existing stack from GitHub (e.g. someone else's)
mergify stack checkout PR_URL # Same, from any PR in the stack — middle included
mergify stack sync # Fetch trunk, remove merged commits, rebase
mergify stack list # Show commit <-> PR mapping for current stack
mergify stack list --json # Same, but machine-readable JSON output
mergify stack reorder C A B # Reorder all commits (pass SHA or Change-Id prefixes)
mergify stack move X first # Move commit X to the top of the stack
mergify stack move X last # Move commit X to the bottom of the stack
mergify stack move X before Y # Move commit X before commit Y
mergify stack move X after Y # Move commit X after commit Y
mergify stack fixup X # Fold commit X into its parent (drops X's message)
mergify stack fixup X Y Z # Fold each into its parent (multi-fixup)
mergify stack squash X into Y # Reorder X adjacent to Y, fold X into Y (keeps Y's message)
mergify stack squash X Y into Z -m "msg" # Fold X Y into Z with a custom message
mergify stack reword X -m "msg" # Change commit X's message non-interactively
mergify stack reword X # Change commit X's message via $GIT_EDITOR (TTY only)
mergify stack edit X # Pause the rebase at X so you can `git commit --amend` it (X is required; no-arg form is interactive)
mergify stack drop X # Drop commit X from the stack
mergify stack drop X Y Z # Drop multiple commits in one rebase
mergify stack note -m "why" # Attach an amend reason to HEAD (shown in PR revision history)
mergify stack note <SHA-or-Change-Id-prefix> -m "why" # Attach to a specific commit in the stack
mergify stack note --append -m "more" # Append to an existing note
mergify stack note --remove # Remove the note from a commit
```
Use `mergify stack checkout` to check out a stack that exists on GitHub (e.g. a colleague's stack). The argument is either the stack's remote branch name — whatever prefix it uses, no assumption that it contains an author — or the URL of any pull request in the stack, including one from the middle. It fetches all stacked PRs, creates a local branch, and sets up tracking.
The local branch defaults to the stack branch with your own stack branch prefix removed, so a stack you pushed from `feature/login` comes back as `feature/login` and a later `mergify stack push` updates the same PRs. For a stack that is not under your prefix — a colleague's — the last segment is used and checkout warns you: you can work on those commits, but `mergify stack push` from that branch would create a separate stack rather than update their pull requests. Use `--branch` to override the name. A pull request URL carries its own `owner/repo`, so `--repository` is ignored in that form.
Use `mergify stack sync` to bring your stack up to date. It fetches the latest trunk, detects which PRs have been merged, removes those commits from your local branch, and rebases the remaining commits. Run this before starting new work on an existing stack.
Use `mergify stack list` to see which commits have been pushed, which PRs they map to, and whether the stack is up to date with the remote. It also shows CI status, review status, and merge conflicts for each PR. Use `--verbose` for detailed check names and reviewer names. Use `--json` when you need to parse the output programmatically — it includes full CI check details and review data.
## GitHub-native stacks (experimental, on by default)
`mergify stack push` registers the stack with GitHub's own Stacks API by
default, so GitHub renders it as a stack. Opt out per invocation with
`--no-github-native`, or per repo with
`git config mergify-cli.stack-github-native false`.
Change-Ids, branch layout, stack comments and revision history are unchanged,
and it degrades quietly: where the API isn't available (older GitHub
Enterprise, a repo without the feature) the push reports
`not registered on GitHub` and succeeds exactly as it would have.
Three things to know:
- **A stack needs at least 2 pull requests.** GitHub rejects a 1-PR stack, so a
single-change stack stays a plain PR — and a 2-PR stack that loses a member
is dissolved rather than re-registered.
- **The `Depends-On:` header goes away.** A registered stack *is* the
dependency between two pull requests, so the CLI stops writing a second copy
of it into the PR descriptions. It is keyed off the registration, not off the
flag: when a push degrades to `not registered on GitHub`, the headers are
written back in the same push and Mergify keeps ordering the stack. Your
commit messages are never touched either way — the header only ever existed
in the rendered PR description.
- **Registering changes how the PRs merge.** While a stack is registered,
GitHub refuses the classic merge endpoint
(`PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge` → 403) for
its members. That is GitHub's contract, not ours; it is the reason
`--no-github-native` exists.
Pushing stays cheap. Refreshing commits (amend, reword, force-push) leaves the
registration untouched, and adding a change on top extends the same stack.
Only a push that moves a pull request's base — a reorder, a drop, a change
inserted in the middle — dissolves the registration first and rebuilds it at
the end, because GitHub rejects any base-branch change while a PR is stacked.
An interrupted push therefore leaves the stack merely unregistered — never
half-registered. It can also leave it without its `Depends-On:` headers, since
those are restored at the end of the push; re-running `mergify stack push`
settles both, because every push re-renders the descriptions and recomputes the
`Depends-On:` markers from the stack's current shape — including with
`--keep-pull-request-title-and-body`, where the description is re-rendered from
the pull request's own body rather than the commit message, and the marker is
still stripped and re-appended rather than carried over.
## Amend Notes
`mergify stack note` records *why* a commit was amended. The note travels with the stack:
- The reason is stored locally under `refs/notes/mergify/stack` against the commit SHA.
- On `mergify stack push`, the reason is consumed into the change's revision history; the note on the pushed head commit is replaced by the **full revision history** (human digest + the `<!-- mergify-revision-data: {...} -->` JSON marker). Git notes — not the PR comment — are the machine-readable source of truth; the PR's "Revision history" comment is rendered from them.
- At merge time, Mergify copies the head commit's history note onto the merge/squash commit, so `git log --notes=mergify/stack` on the base branch shows why each change was revised.
**When to attach a note** — any time you amend or rewrite a commit that already has a PR open (i.e. it has been pushed at least once). The note answers "why is this revision different?" so the reviewer doesn't have to diff old vs new SHAs to find out.
**Workflow** — attach the note BEFORE `mergify stack push`:
```bash
# Edit HEAD, then:
git commit --amend
mergify stack note -m "address review: rename foo() to bar()"
mergify stack push
# Or for a mid-stack commit (after the rebase that amended it):
mergify stack note <SHA-or-Change-Id-prefix> -m "fix lint reported in CI"
mergify stack push
```
A note is per-commit, not per-revision. Each amend (or other history rewrite) creates a new commit SHA, so you must run `mergify stack note` again for the new SHA — the previous note stays attached to the old SHA and won't carry over. Use `--append` only when the current target commit already has a note and you want to add another reason; use `--remove` to clear it. Notes on commits that haven't changed since the last push are preserved but won't add a new revision row.
## CRITICAL: Check Branch Before ANY Commit
**BEFORE staging or committing anything**, always check the current branch and assess stack state:
```bash
git branch --show-current
mergify stack list
```
- If you're on `main` (or the repo's default branch): you **MUST** create a feature branch first
- **NEVER commit directly on `main`** — `mergify stack push` will fail
- This check must happen before `git add`, not after `git commit`
## CRITICAL: Stash Local Changes Before Worktree-Modifying Operations
**BEFORE running any operation that rewrites history or switches branches**, check for uncommitted changes and stash them:
```bash
git status --short # Check for uncommitted changes
git stash -u # Stash tracked + untracked changes if any
```
**Operations that require this check:**
- `mergify stack edit <commit>` (mid-stack fixes)
- `mergify stack reorder` / `mergify stack move`
- `mergify stack fixup`
- `mergify stack squash`
- `mergify stack reword`
- `mergify stack drop`
- `mergify stack checkout`
- `mergify stack sync`
- `mergify stack new` (switches to new branch)
- `git checkout <branch>`
**After the operation completes**, restore the stashed changes:
```bash
git stash pop
```
If you skip this step, uncommitted work will be **silently lost** or cause rebase conflicts.
## Starting New Work
When asked to start a new piece of work, create a new feature, or work on something unrelated to the current stack:
1. **Check current branch**: `git branch --show-current`
2. **Create a new stack**: `mergify stack new <branch-name>`
- Use descriptive branch names following the pattern: `type/short-description` (e.g., `feat/add-login`, `fix/memory-leak`)
3. **Make commits** following conventional commits
4. **Push**: `mergify stack push`
## Adding to Existing Stack
When continuing work on an existing feature branch:
1. **Check current branch**: `git branch --show-current`
- If on the right branch: proceed with commits
- If on `main`: switch to the feature branch first with `git checkout <branch>` or create a new stack
## Conflict Resolution
When a rebase causes conflicts (during `git rebase -i` or `mergify stack push`):
1. Resolve conflicts in your editor
2. Stage resolved files with `git add`
3. Continue with `git rebase --continue`
To abort instead: `git rebase --abort`
After resolving, run `mergify stack push` to sync the updated stack.
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package license
- Apache-2.0
- Package author
- Mergify
- Keywords
- git, pr, stacked-prs, merge-queue, ci, test-insights, flaky-tests, quarantine, freeze, config, push, commit, branch, rebase, checkout, workflow
Declared capabilities
- Interactive
- Read
- Write
Package observed Oct 2, 2026.
Technical details
- First seen
- Sep 30, 2026 · 22:02 UTC
- Last seen
- Oct 2, 2026 · 18:00 UTC
- Collection status
- Collected
plugins_6aabe2c8f7b88191a6962623c8199c7b
Download plugin data (JSON)