View as Markdown

Scopes with Nx

Configure merge queue scopes using Nx's dependency graph to drive batching.


If you’re using tools like the Nx build system that have built-in dependency graph analysis, you can use their affected project detection instead of file patterns. This approach is often more accurate because these tools understand your project’s dependency relationships.

To use the manual scopes mechanism, configure Mergify to expect scopes from your CI system:

scopes:
source:
manual:
queue_rules:
- name: default
batch_size: 5

Use the nx show projects command to determine affected projects and upload them to Mergify.

name: Detect Scopes
on:
pull_request:
jobs:
detect-scopes:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
with:
# The queue-aware base commit must exist locally: nx resolves the
# base/head range through git.
fetch-depth: 0
- name: Get git refs
id: refs
uses: Mergifyio/gha-mergify-ci@v25
with:
action: scopes-git-refs
- name: Get scopes
id: scopes
env:
HEAD: ${{ steps.refs.outputs.head }}
BASE: ${{ steps.refs.outputs.base }}
run: |
# scopes-git-refs reports an empty base on any event that is neither
# a pull request nor a push. Diffing against an empty revision, or
# letting the build tool fall back to its own default base, reports
# scopes for the wrong range.
[ -n "$BASE" ] || { echo "no base ref reported" >&2; exit 1; }
# A failed nx piped into paste still exits 0 and writes an empty
# scope list, which the queue reads as "this pull request affects
# nothing", so capture the projects first.
projects=$(npx nx show projects --affected --base="$BASE" --head="$HEAD") || exit 1
scopes=$(printf '%s\n' "$projects" | paste -sd, -)
echo "scopes=$scopes" >> "$GITHUB_OUTPUT"
- name: Scopes upload
uses: Mergifyio/gha-mergify-ci@v25
with:
action: scopes-upload
token: ${{ secrets.MERGIFY_TOKEN }}
scopes: ${{ steps.scopes.outputs.scopes }}

Using the mergifyio/mergify-ci Buildkite plugin, a first step resolves the merge-queue-aware base and head SHAs and exposes them as meta-data, a second computes the affected projects with Nx into the mergify-ci.scopes meta-data, and a third uploads them. The upload runs in its own step because the plugin replaces the step’s command, so a step that both detects and uploads never runs its detection:

steps:
- label: ":mag: Get git refs"
key: git-refs
plugins:
- mergifyio/mergify-ci#v7:
action: scopes-git-refs
- label: ":mag: Detect scopes"
key: detect-scopes
depends_on: git-refs
command: |
# The base meta-data is only published when a base was resolved, so a
# missing key means there is no base to diff against. Check the value
# too: an empty one would send the build tool back to its own default
# base and report scopes for the wrong range.
BASE=$(buildkite-agent meta-data get "mergify-ci.base") || exit 1
HEAD=$(buildkite-agent meta-data get "mergify-ci.head") || exit 1
[ -n "$$BASE" ] || { echo "no base ref reported" >&2; exit 1; }
# A failed nx piped into paste still exits 0 and writes an empty scope
# list, which the queue reads as "this pull request affects nothing",
# so capture the projects first.
PROJECTS=$(npx nx show projects --affected --base="$$BASE" --head="$$HEAD") || exit 1
SCOPES=$(printf '%s\n' "$$PROJECTS" | paste -sd, -)
buildkite-agent meta-data set "mergify-ci.scopes" "$$SCOPES"
- label: ":mag: Upload scopes"
depends_on: detect-scopes
plugins:
- mergifyio/mergify-ci#v7:
action: scopes-upload

The plugin reads your application key from MERGIFY_TOKEN in the agent’s environment. Without it the step still runs, but it logs a warning and sends nothing, and the build stays green, so a missing token looks like a pipeline that reports no scopes.

Install the Mergify CLI in your pipeline and export MERGIFY_TOKEN. Use mergify ci git-refs to get the merge-queue-aware base and head SHAs and mergify ci scopes-send to upload the detected scopes:

Terminal window
REFS=$(mergify ci git-refs --format json)
# -e so a null ref fails here instead of flowing on as the literal string
# "null": git-refs reports no base outside pull request and push events.
BASE=$(printf '%s' "$REFS" | jq -er '.base') || exit 1
HEAD=$(printf '%s' "$REFS" | jq -er '.head') || exit 1
# A failed nx piped into jq still exits 0 and writes a well-formed, empty
# scopes.json, which the queue reads as "this pull request affects nothing".
PROJECTS=$(npx nx show projects --affected --base="$BASE" --head="$HEAD") || exit 1
printf '%s\n' "$PROJECTS" \
| jq -R -s '{scopes: split("\n") | map(select(length > 0))}' > scopes.json
mergify ci scopes-send --scopes-json scopes.json

Was this page helpful?