A Buildkite plugin for integrating with Mergify CI Insights — upload JUnit test reports, detect pull request scopes, and upload scopes to Mergify Merge Queue.
Put the Mergify token in MERGIFY_TOKEN in the agent environment. None of the
examples below pass it through the plugin's token config, on purpose.
buildkite-agent pipeline upload interpolates variables at upload time, so
token: "${MERGIFY_TOKEN}" writes the plaintext secret into the stored
pipeline, where the Buildkite UI renders it and build history keeps it. The
agent warns about it on upload, and Buildkite Agent v4 rejects such uploads by
default: v3's opt-in --reject-secrets becomes an opt-out --allow-secrets.
Escaping does not help. $$MERGIFY_TOKEN survives upload as the literal string
$MERGIFY_TOKEN, and the plugin prefers its token config over the
environment, so that non-empty string shadows the MERGIFY_TOKEN that would
have worked and is sent as the token.
Supply MERGIFY_TOKEN the way you supply any other
Buildkite secret,
so it only ever exists in the job's environment:
- an agent
environmenthook that exportsMERGIFY_TOKEN - a secrets plugin that exports
MERGIFY_TOKENinto the job from its ownenvironmenthook, which runs before this plugin resolves the token
A pipeline-level env: block is not one of these: it is stored with the
pipeline just like token is.
Unsetting MERGIFY_TOKEN does not necessarily stop a build from
authenticating, because mergify-cli falls back to GITHUB_TOKEN and then to
gh auth token. With none of those available, scopes still detects scopes and
writes the meta-data and skips only the API upload with a warning,
scopes-upload warns and skips, and junit-process fails the step.
The examples pin mergifyio/mergify-ci#v7. Newer tags are on the
releases page;
the pins here are bumped once a tag exists, so they can trail the newest release
by a day.
Coming from #v2 or older, drop python_version from the plugin config before
you move. v5 removed that property and the schema sets
additionalProperties: false, so a step still passing it fails validation. Pin
the CLI with mergify_cli_version instead. v5 also replaced the uv install
with a prebuilt binary, so the agent needs tar and sha256sum/shasum rather
than Python, see Requirements.
Process JUnit XML test reports and upload them to Mergify CI Insights. Detects silent test failures automatically using the step's exit code.
steps:
- label: "Run tests"
command: pytest --junitxml=reports/junit.xml
plugins:
- mergifyio/mergify-ci#v7:
action: junit-process
report_path: "reports/*.xml"Detect which code scopes are affected by a pull request and upload them to the Mergify API. A Buildkite annotation is created to display the detected scopes directly on the build page.
steps:
- label: "Detect scopes"
plugins:
- mergifyio/mergify-ci#v7:
action: scopesReturn the merge-queue-aware base and head SHAs. Results are stored as Buildkite meta-data (mergify-ci.base, mergify-ci.head) for use by subsequent steps.
steps:
- label: "Get git refs"
key: git-refs
plugins:
- mergifyio/mergify-ci#v7:
action: scopes-git-refsUpload scopes to the Mergify API. Requires a prior scopes-git-refs step for base/head refs. Scopes can be provided as a comma-separated list in plugin config, or read automatically from the mergify-ci.scopes meta-data set by a prior scopes step (both JSON and CSV formats are supported).
# Option 1: explicit scopes via plugin config
steps:
- label: "Get git refs"
key: git-refs
plugins:
- mergifyio/mergify-ci#v7:
action: scopes-git-refs
- label: "Upload scopes"
depends_on: git-refs
plugins:
- mergifyio/mergify-ci#v7:
action: scopes-upload
scopes: "backend,frontend"# Option 2: scopes from meta-data (set by a prior step)
steps:
- label: "Generate scopes"
key: generate-scopes
command: |
buildkite-agent meta-data set "mergify-ci.scopes" "backend,frontend"
- label: "Upload scopes"
depends_on: generate-scopes
plugins:
- mergifyio/mergify-ci#v7:
action: scopes-uploadDetect scopes first, then use the meta-data to skip steps unaffected by the change:
steps:
- label: "Detect scopes"
key: scopes
plugins:
- mergifyio/mergify-ci#v7:
action: scopes
- label: "Backend tests"
depends_on: scopes
command: pytest tests/backend/
if: build.env("BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG") != null || build.pull_request.id != null
plugins:
- mergifyio/mergify-ci#v7:
action: junit-process
report_path: "reports/*.xml"
# Use a dynamic pipeline or script to check scopes:
# SCOPES=$(buildkite-agent meta-data get "mergify-ci.scopes")
# echo "$SCOPES" | jq -e '.backend == "true"'
- label: "Frontend tests"
depends_on: scopes
command: npm test
plugins:
- mergifyio/mergify-ci#v7:
action: junit-process
report_path: "reports/*.xml"
# SCOPES=$(buildkite-agent meta-data get "mergify-ci.scopes")
# echo "$SCOPES" | jq -e '.frontend == "true"'For full conditional control, use a dynamic pipeline that reads the scopes meta-data and only uploads the relevant steps:
#!/bin/bash
# .buildkite/dynamic-pipeline.sh
SCOPES=$(buildkite-agent meta-data get "mergify-ci.scopes")
if echo "$SCOPES" | jq -e '.backend == "true"' > /dev/null 2>&1; then
cat <<'YAML'
- label: "Backend tests"
command: pytest tests/backend/
plugins:
- mergifyio/mergify-ci#v7:
action: junit-process
report_path: "reports/*.xml"
YAML
fi
if echo "$SCOPES" | jq -e '.frontend == "true"' > /dev/null 2>&1; then
cat <<'YAML'
- label: "Frontend tests"
command: npm test
plugins:
- mergifyio/mergify-ci#v7:
action: junit-process
report_path: "reports/*.xml"
YAML
fi# pipeline.yml
steps:
- label: "Detect scopes"
key: scopes
plugins:
- mergifyio/mergify-ci#v7:
action: scopes
- label: "Upload pipeline"
depends_on: scopes
command: .buildkite/dynamic-pipeline.sh | buildkite-agent pipeline uploadAuthentication is not one of these properties: the token comes from
MERGIFY_TOKEN in the agent environment, see Authentication.
| Property | Required | Default | Description |
|---|---|---|---|
action |
yes | — | junit-process, scopes, scopes-git-refs, or scopes-upload |
token |
no | — | Mergify token taken from plugin config. Plugin config is stored with the pipeline, so only set this to a value that is not a secret, such as a mock token. See Authentication |
report_path |
for junit-process | — | Glob path to JUnit XML files |
scopes |
no | — | Comma-separated list of scopes. If not set, scopes-upload reads from mergify-ci.scopes meta-data |
mergify_api_url |
no | https://api.mergify.com |
Mergify API endpoint |
job_name |
no | Step label | Override job name (useful for matrix builds) |
mergify_config_path |
no | — | Path to .mergify.yml configuration file |
mergify_cli_version |
no | pinned default | Version of mergify-cli to install. Leave unset to use the plugin's pinned default (kept current by Renovate, see hooks/environment), pin an exact release (e.g. 2026.6.16.1, must be >= 2026.6.15.1), or use latest to always install the newest released version. |
The plugin stores the following values via buildkite-agent meta-data:
| Key | Set by | Description |
|---|---|---|
mergify-ci.base |
scopes, scopes-git-refs |
Merge-queue-aware base SHA |
mergify-ci.head |
scopes, scopes-git-refs |
Merge-queue-aware head SHA |
mergify-ci.source |
scopes, scopes-git-refs |
Reference source (e.g. buildkite_pull_request, merge_queue) |
mergify-ci.scopes |
scopes |
JSON mapping of scope names to "true"/"false" |
The mergify-cli writes Buildkite annotations (context: mergify-ci-scopes) showing the detected scopes as a table. Annotations are written at both build scope (visible on the build page) and job scope (visible on the job card).
mergify-cli is installed as a prebuilt binary (no Python required), downloaded
and checksum-verified by the upstream install.sh.
Supported platforms: Linux (x86_64, aarch64) and macOS (x86_64, aarch64).
curl(to download the installer and the binary)tar(to extract the binary)sha256sumorshasum(to verify the binary checksum)jq(for JSON processing inscopes-upload)
The suite drives the plugin for real, so it needs the same jq listed under
Requirements. Without it the scopes-upload tests fail with
exit 127.
bats tests/CI runs them in GitHub Actions (.github/workflows/ci.yaml), which reports the
ci-gate check that merges are gated on.
Apache 2.0 — see LICENSE.