Skip to content

fix(docs): detect Bazel scopes from impacted targets, not changed files - #12476

Draft
jd wants to merge 1 commit into
mainfrom
devs/jd/jd/mrgfy-8808-bazel-scope-detection-under-reports-buildfiles-misses/detect-bazel-scopes-impacted-targets-changed-files--432e2703
Draft

fix(docs): detect Bazel scopes from impacted targets, not changed files#12476
jd wants to merge 1 commit into
mainfrom
devs/jd/jd/mrgfy-8808-bazel-scope-detection-under-reports-buildfiles-misses/detect-bazel-scopes-impacted-targets-changed-files--432e2703

Conversation

@jd

@jd jd commented Aug 21, 2026

Copy link
Copy Markdown
Member

All three CI recipes on the Bazel scopes page ran the same detection:

bazel query --output=package "buildfiles(set(<changed files>))"

buildfiles() returns the BUILD and .bzl files needed to load the
packages the changed files live in. It walks the graph away from the
dependents, so a change to a shared library tags that library and
nothing downstream — while the Nx page next door uses
nx show projects --affected, which does include dependents. Two
sibling pages promised the same guarantee and only one delivered it.

That is a correctness bug rather than imprecision. Scopes are what makes
the queue serialize pull requests that touch the same area; an
under-reported list lets it batch and parallelize pull requests that
genuinely conflict, and lets scope-gated CI jobs skip work the change
really did affect.

The page now teaches bazel-diff (github.com/Tinder/bazel-diff), which
hashes every target over its content, rule attributes and transitive
deps at two revisions and reports the targets whose hash changed.

Verified by running both detections against two Bazel workspaces:

  • a four-package fixture (//app/server -> //lib/util -> //lib/core,
    every BUILD loading a macro from //tools), across six scenarios;
  • Tinder/bazel-diff itself at HEAD~10..HEAD, on Bazel 8.5.1.

What the runs showed, beyond the known missing dependents:

  • buildfiles() names the wrong side of the graph. tools — the
    package that defines the macro — appears for every change, while
    the packages that consume it never do.
  • On a real workspace it emits junk. On Tinder/bazel-diff it returned
    ~60 external-repository pseudo-packages (@rules_kotlin//kotlin,
    @bazel_tools//tools/build_defs/repo, ...) alongside 7 real ones,
    because it also returns the .bzl files of every ruleset loaded.
    Those get uploaded verbatim as scope names. It also missed //proto,
    //release and //tools/go/sample, which bazel-diff found.

No product work was needed: source: manual, scopes-git-refs,
scopes-upload and mergify ci scopes-send already carry this.

The page keeps no "quick but approximate" build-graph option, because
there isn't one. rdeps(//..., set(<changed files>)) was measured too:
it returns nothing at all for a pull request that only edits BUILD or
.bzl files, since those are not targets and rdeps of an empty set is
empty. Zero scopes reads as "impacts nothing", which is worse than the
status quo. Readers who do not want a JVM and a second checkout are
pointed at file-pattern scopes instead — coarse, but honest about it.

The recipe also documents the traps the runs surfaced: --targetType Rule throws unless generate-hashes ran with --includeTargetType;
bazel writes MODULE.bazel.lock so the mid-recipe checkout needs
--force; fetch-depth: 0 is now mandatory because the base revision
must exist locally; and hashing both revisions in one directory keeps
the Bazel server warm (15.4s cold then 4.5s warm on the real repo).

This builds on #12468, which made these snippets actually run: it fixed
the ref quoting, added fetch-depth: 0 and stopped the query failing
silently into an empty scope list. That work was about the plumbing
around bazel query; this one changes the question the query asks, so
the quoting, exit-3 and empty-diff handling it added go away with the
git diff | bazel query pipeline they guarded. The fetch-depth: 0 it
added stays, and is now load-bearing for a different reason: bazel-diff
checks the base revision out rather than diffing against it.

The GitHub Actions and Buildkite wirings around the shell are unchanged
in shape from the existing page and were not executed on a real runner.

MRGFY-8808

Copilot AI lite review requested due to automatic review settings August 21, 2026 09:09
@mergify
mergify Bot had a problem deploying to Mergify Merge Protections August 21, 2026 09:09 Failure
@mergify

mergify Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 2 of 6 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 👀 Review Requirements 👀 reviews
🔴 🔎 Reviews 👀 reviews
🟢 🤖 Continuous Integration
🟢 Enforce conventional commit
🟢 📕 PR description
🟢 🚦 Auto-queue

🔴 👀 Review Requirements

Waiting for

  • #approved-reviews-by >= 2
This rule is failing.
  • any of:
    • #approved-reviews-by >= 2
    • author = dependabot[bot]
    • all of:
      • author = mergify-ci-bot
      • -head ~= ^docs-agent/

🔴 🔎 Reviews

Waiting for

  • #review-requested = 0
This rule is failing.
  • #review-requested = 0
  • #changes-requested-reviews-by = 0
  • #review-threads-unresolved = 0

Show 4 satisfied protections

🟢 🤖 Continuous Integration

  • all of:
    • check-success = build
    • check-success = lint
    • check-success = test
    • any of:
      • check-success = test-broken-links
      • label = ignore-broken-links
    • any of:
      • check-success=Cloudflare Pages
      • -head-repo-full-name~=^Mergifyio/

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|internal|docs|style|refactor|perf|test|build|ci|chore|revert|ui)(?:\(.+\))?!?:

🟢 📕 PR description

  • body ~= (?ms:.{48,})

🟢 🚦 Auto-queue

When all merge protections are satisfied, this pull request will be queued automatically.

@jd
jd force-pushed the devs/jd/jd/mrgfy-8808-bazel-scope-detection-under-reports-buildfiles-misses/detect-bazel-scopes-impacted-targets-changed-files--432e2703 branch from b7cef70 to 5d9cffb Compare August 21, 2026 09:13
@jd

jd commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

Revision history

# Type Changes Reason Date
1 initial b7cef70 2026-08-21 09:13 UTC
2 content b7cef70 → 5d9cffb fix lint: remark-lint-list-item-spacing wants a blank line between multi-line list items 2026-08-21 09:13 UTC
3 content 5d9cffb → a56d493 review: verify the bazel-diff JAR before running it — pinned sha256 in all four recipes (guarded with || exit 1 so the CLI snippet, which has no set -e, aborts too), plus a Verifying the download s… 2026-08-21 09:32 UTC

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Bazel scopes documentation to compute merge-queue scopes from impacted Bazel targets (via bazel-diff) instead of inferring scopes from changed files/packages, aligning the “dependency-graph-based scopes” promise with how affected-graph detection is described elsewhere in the docs.

Changes:

  • Replaces bazel query buildfiles(set(<changed files>)) guidance with a bazel-diff-based impacted-targets workflow.
  • Adds an explanation of why changed-file/package queries under-report impact and can produce misleading scope lists.
  • Updates GitHub Actions, Buildkite, and CLI recipes and adds guidance on performance/cost and “graph-invisible” changes.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/content/docs/merge-queue/scopes/bazel.mdx
@mergify
mergify Bot had a problem deploying to Mergify Merge Protections August 21, 2026 09:15 Failure
@mergify
mergify Bot requested a review from a team August 21, 2026 09:16
All three CI recipes on the Bazel scopes page ran the same detection:

    bazel query --output=package "buildfiles(set(<changed files>))"

`buildfiles()` returns the BUILD and .bzl files needed to *load* the
packages the changed files live in. It walks the graph away from the
dependents, so a change to a shared library tags that library and
nothing downstream — while the Nx page next door uses
`nx show projects --affected`, which does include dependents. Two
sibling pages promised the same guarantee and only one delivered it.

That is a correctness bug rather than imprecision. Scopes are what makes
the queue serialize pull requests that touch the same area; an
under-reported list lets it batch and parallelize pull requests that
genuinely conflict, and lets scope-gated CI jobs skip work the change
really did affect.

The page now teaches bazel-diff (github.com/Tinder/bazel-diff), which
hashes every target over its content, rule attributes and transitive
deps at two revisions and reports the targets whose hash changed.

Verified by running both detections against two Bazel workspaces:

  - a four-package fixture (//app/server -> //lib/util -> //lib/core,
    every BUILD loading a macro from //tools), across six scenarios;
  - Tinder/bazel-diff itself at HEAD~10..HEAD, on Bazel 8.5.1.

What the runs showed, beyond the known missing dependents:

  - `buildfiles()` names the wrong side of the graph. `tools` — the
    package that *defines* the macro — appears for every change, while
    the packages that consume it never do.
  - On a real workspace it emits junk. On Tinder/bazel-diff it returned
    ~60 external-repository pseudo-packages (`@rules_kotlin//kotlin`,
    `@bazel_tools//tools/build_defs/repo`, ...) alongside 7 real ones,
    because it also returns the .bzl files of every ruleset loaded.
    Those get uploaded verbatim as scope names. It also missed //proto,
    //release and //tools/go/sample, which bazel-diff found.

No product work was needed: `source: manual`, `scopes-git-refs`,
`scopes-upload` and `mergify ci scopes-send` already carry this.

The page keeps no "quick but approximate" build-graph option, because
there isn't one. `rdeps(//..., set(<changed files>))` was measured too:
it returns *nothing at all* for a pull request that only edits BUILD or
.bzl files, since those are not targets and `rdeps` of an empty set is
empty. Zero scopes reads as "impacts nothing", which is worse than the
status quo. Readers who do not want a JVM and a second checkout are
pointed at file-pattern scopes instead — coarse, but honest about it.

The recipe also documents the traps the runs surfaced: `--targetType
Rule` throws unless `generate-hashes` ran with `--includeTargetType`;
bazel writes MODULE.bazel.lock so the mid-recipe checkout needs
`--force`; `fetch-depth: 0` is now mandatory because the base revision
must exist locally; and hashing both revisions in one directory keeps
the Bazel server warm (15.4s cold then 4.5s warm on the real repo).

This builds on #12468, which made these snippets actually run: it fixed
the ref quoting, added fetch-depth: 0 and stopped the query failing
silently into an empty scope list. That work was about the plumbing
around `bazel query`; this one changes the question the query asks, so
the quoting, exit-3 and empty-diff handling it added go away with the
`git diff | bazel query` pipeline they guarded. The fetch-depth: 0 it
added stays, and is now load-bearing for a different reason: bazel-diff
checks the base revision out rather than diffing against it.

The GitHub Actions and Buildkite wirings around the shell are unchanged
in shape from the existing page and were not executed on a real runner.

MRGFY-8808

Change-Id: I432e27032980e1106d41e1380233177bb3c0560e
@jd
jd force-pushed the devs/jd/jd/mrgfy-8808-bazel-scope-detection-under-reports-buildfiles-misses/detect-bazel-scopes-impacted-targets-changed-files--432e2703 branch from 5d9cffb to a56d493 Compare August 21, 2026 09:32
@mergify
mergify Bot had a problem deploying to Mergify Merge Protections August 21, 2026 09:33 Failure
@jd

jd commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

Force-pushed — 5d9cffb → a56d493.

The review asked for a supply-chain note on the bazel-diff JAR the recipes download and java -jar. The version was already pinned; the bytes were not, so all four snippets now verify the digest before running it, guarded with || exit 1 — the Any CI snippet is emitted by <ScopesDetection> with no set -e, so without the guard a failed checksum would print FAILED and the next line would run the JAR anyway.

There is also a new Verifying the download section: recomputing the digest on a version bump, gh attestation verify against the SLSA provenance shipped beside the JAR, and a note that sha256sum is GNU coreutils so macOS agents need shasum -a 256 -c -.

Everything in it was executed, not written from memory — the digest matches the real v42.0.0 artifact, gh attestation verify exits 0 on it and 1 on a tampered copy, and the || exit 1 guard was tested against a script with no set -e. pnpm check, pnpm check:internal-leaks and pnpm build are green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants