---
title: Scopes with Bazel
description: Configure merge queue scopes using Bazel's dependency graph for precise batching.
---

If you're using monorepo tools like Bazel 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.

Here's the whole setup running on a Bazel monorepo, from the `bazel query` step in CI to
several packages testing in their own lanes at once:

<Youtube video="oJmidhu0AIA" title="Parallel merge queues for Bazel monorepos" />

## Configuring Manual Scopes

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

```yaml
scopes:
  source:
    manual:

queue_rules:
  - name: default
    batch_size: 5
```

## Detecting Scopes with Bazel

Use `bazel query` to determine affected projects and upload them to Mergify.

### GitHub Actions

```yaml
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 for `git diff`.
          fetch-depth: 0

      - name: Get git refs
        id: refs
        uses: Mergifyio/gha-mergify-ci@@@GHA_MERGIFY_CI_VERSION@@
        with:
          action: scopes-git-refs

      - name: Get scopes
        id: scopes
        env:
          HEAD: ${{ steps.refs.outputs.head }}
          BASE: ${{ steps.refs.outputs.base }}
        run: |
          changed=$(git diff --name-only --diff-filter=ACMR "$BASE" "$HEAD" | tr '\n' ' ')
          if [ -z "$changed" ]; then
            echo "scopes=" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          set +e
          packages=$(bazel query --keep_going --noshow_progress --output=package \
            "buildfiles(set($changed))" 2>query.err)
          rc=$?
          set -e
          # --keep_going exits 3 when it skipped files outside any Bazel package
          # (README.md, .github/**). Anything else is a real failure and must not
          # be swallowed into an empty scope list.
          if [ "$rc" -ne 0 ] && [ "$rc" -ne 3 ]; then
            cat query.err >&2
            exit "$rc"
          fi

          scopes=$(printf '%s\n' "$packages" | sed '/^$/d' | sort -u | paste -sd, -)
          echo "Detected scopes: ${scopes:-<none>}"
          echo "scopes=$scopes" >> "$GITHUB_OUTPUT"

      - name: Scopes upload
        uses: Mergifyio/gha-mergify-ci@@@GHA_MERGIFY_CI_VERSION@@
        with:
          action: scopes-upload
          token: ${{ secrets.MERGIFY_TOKEN }}
          scopes: ${{ steps.scopes.outputs.scopes }}
```

### Buildkite

Using the
[`mergifyio/mergify-ci`](https://github.com/Mergifyio/mergify-ci-buildkite-plugin)
Buildkite plugin, a first step resolves the merge-queue-aware base and head
SHAs and exposes them as meta-data, while a second step computes the affected
projects with `bazel query` and uploads them to Mergify:

```yaml
steps:
  - label: ":mag: Get git refs"
    key: git-refs
    plugins:
      - mergifyio/mergify-ci#@@BUILDKITE_PLUGIN_VERSION@@:
          action: scopes-git-refs

  - label: ":mag: Detect and upload scopes"
    depends_on: git-refs
    command: |
      BASE=$(buildkite-agent meta-data get "mergify-ci.base")
      HEAD=$(buildkite-agent meta-data get "mergify-ci.head")
      CHANGED=$(git diff --name-only --diff-filter=ACMR "$BASE" "$HEAD" | tr '\n' ' ')
      SCOPES=""
      if [ -n "$CHANGED" ]; then
        set +e
        PACKAGES=$(bazel query --keep_going --noshow_progress --output=package \
          "buildfiles(set($CHANGED))" 2>query.err)
        RC=$?
        set -e
        # --keep_going exits 3 when it skipped files outside any Bazel package.
        # Anything else is a real failure, not an empty scope list.
        if [ "$RC" -ne 0 ] && [ "$RC" -ne 3 ]; then
          cat query.err >&2
          exit "$RC"
        fi
        SCOPES=$(printf '%s\n' "$PACKAGES" | sed '/^$/d' | sort -u | paste -sd, -)
      fi
      buildkite-agent meta-data set "mergify-ci.scopes" "$SCOPES"
    plugins:
      - mergifyio/mergify-ci#@@BUILDKITE_PLUGIN_VERSION@@:
          action: scopes-upload
          token: "${MERGIFY_TOKEN}"
```

### Any CI (Mergify CLI)

<ScopesDetection
  command={String.raw`CHANGED=$(git diff --name-only --diff-filter=ACMR "$BASE" "$HEAD" | tr '\n' ' ')

if [ -z "$CHANGED" ]; then
  echo '{"scopes": []}' > scopes.json
else
  # Capture the exit code rather than piping straight into jq: a failed query
  # piped into jq still writes a well-formed, empty scopes.json and returns 0.
  if PACKAGES=$(bazel query --keep_going --noshow_progress --output=package \
                  "buildfiles(set($CHANGED))" 2>query.err); then
    RC=0
  else
    RC=$?
  fi

  # --keep_going exits 3 when it skipped files outside any Bazel package
  # (README.md, .github/**). Anything else is a real failure.
  if [ "$RC" -ne 0 ] && [ "$RC" -ne 3 ]; then
    cat query.err >&2
    exit "$RC"
  fi

  printf '%s\n' "$PACKAGES" | sort -u \
    | jq -R -s '{scopes: split("\n") | map(select(length > 0))}' > scopes.json
fi`}
/>
