Skip to content

feat: add --alwaysAffectedTags to always impact non-hermetic tagged targets (#401)#410

Merged
tinder-maxwellelliott merged 2 commits into
masterfrom
claude/objective-proskuriakova-8110ff
Jul 6, 2026
Merged

feat: add --alwaysAffectedTags to always impact non-hermetic tagged targets (#401)#410
tinder-maxwellelliott merged 2 commits into
masterfrom
claude/objective-proskuriakova-8110ff

Conversation

@tinder-maxwellelliott

Copy link
Copy Markdown
Collaborator

Problem

Closes #401.

bazel-diff hashes a target from its declared rule inputs (plus transitive deps in the query graph). A target that reads the workspace at execution time without declaring those files as inputs — a repo-scanning linter such as buildifier_test, gofmt_test, or eslint_test — keeps a stable hash across commits that only change those undeclared files. Under target determination, the target is then wrongly skipped even though it would fail if run.

Monorepos commonly tag these non-hermetic targets with a Bazel target tag (the issue uses external). There was no first-class way to say "always treat targets with this tag as impacted" — the existing knobs don't fit (--seed-filepaths over-invalidates the whole graph; --excludeExternalTargets / --fineGrainedHashExternalRepos are about external repos, not the tag; --cqueryExpression only sees declared deps).

Change

New generate-hashes flag: --alwaysAffectedTags (comma-separated, e.g. --alwaysAffectedTags=external).

Any rule whose tags attribute contains a listed value gets a per-invocation random sentinel mixed into its hash, so a diff of the base and head hash files always reports it as impacted. The tag name is the caller's convention — nothing is hardcoded.

bazel-diff generate-hashes --alwaysAffectedTags=external -w "$WORKSPACE" -b "$BAZEL" hashes.json

How it works

  • BazelRule.tags() reads the tags STRING_LIST attribute from the query/cquery proto.
  • RuleHasher mixes the sentinel into the tagged rule's direct digest, so it is classified DIRECT-impacted for build-graph distance metrics. It also bubbles into the overall digest, so any rdeps are conservatively re-hashed too.
  • The sentinel is minted once per invocation in hasherModule, and only when the flag is set. When the feature is unused nothing is mixed, so every hash is byte-for-byte identical to before and untagged targets never over-invalidate.

Design notes for reviewers

  • Sentinel = per-invocation nonce, not HEAD commit SHA. The issue floated both. The nonce was chosen because core generate-hashes has no git dependency, and it guarantees "always affected" even for non-git workspaces, shallow clones, dirty trees, and same-commit re-runs (a commit SHA would not mark the target affected when base == head). Trade-off: tagged targets' hashes are intentionally non-deterministic across runs; everything untagged stays deterministic.
  • Cascade is intentional. Mixing into the digest necessarily flows into the overall digest that dependents consume, so rdeps of a tagged target are re-hashed. For the leaf test targets in the motivating issue there are no rdeps; for the general case it's a safe over-approximation. Documented in code + README.
  • Scope is limited to generate-hashes; serve / get-impacted-targets pass the (defaulted-empty) parameter unchanged.

Tests

  • New RuleHasherAlwaysAffectedTagsTest: tagged target changes across invocations (overall + direct digest), untagged/unlisted-tag targets stay stable, enabling the flag doesn't perturb untagged targets, and it's inert when no tags are configured.
  • BazelRuleTest: tags() extraction (present / absent).
  • BazelDiffTest: CLI parsing of --alwaysAffectedTags (and empty default).

bazel test //cli:all → 34/35 pass locally. The lone failure is //cli:E2ETest, which is environmental in this sandbox (nested Bazel: Cannot find Java binary bin/java, simulated network error resolving repository rule); it needs a full toolchain + network and never sets the new flag, so this change cannot affect it.

Docs

  • README: new "Always-affected targets (non-hermetic tags)" section with a usage example, plus the regenerated generate-hashes --help block.

🤖 Generated with Claude Code

…argets (#401)

bazel-diff hashes a target from its declared rule inputs, so a non-hermetic
target that reads undeclared workspace state at execution time (a repo-scanning
linter such as buildifier_test / gofmt_test / eslint_test) keeps a stable hash
when only those undeclared files change. Target determination then wrongly skips
it even though it would fail if run.

Add a `generate-hashes` flag, `--alwaysAffectedTags` (comma-separated), that opts
such targets in by Bazel target tag. Any rule whose `tags` attribute contains a
listed value gets a per-invocation random sentinel mixed into its digest, so a
diff of two generate-hashes runs always marks it impacted.

- BazelRule.tags() reads the `tags` STRING_LIST attribute from the query proto.
- RuleHasher mixes the sentinel into the *direct* digest of tagged rules, so they
  are classified DIRECT-impacted for distance metrics; it also bubbles into the
  overall digest, conservatively re-hashing any rdeps.
- The sentinel is minted once per invocation in hasherModule, and only when the
  flag is set. When unused, nothing is mixed and every hash is byte-for-byte
  identical to before, so untagged targets never over-invalidate.

Chose a per-invocation nonce over a HEAD commit SHA (both were floated in the
issue): core generate-hashes has no git dependency, and the nonce guarantees
"always affected" even for non-git workspaces, shallow clones, dirty trees, and
same-commit re-runs.

This is unrelated to --excludeExternalTargets / --fineGrainedHashExternalRepos,
which concern external *repositories*, not the `external` *tag*.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tinder-maxwellelliott tinder-maxwellelliott force-pushed the claude/objective-proskuriakova-8110ff branch from 200c8f4 to 7b9cbf2 Compare July 6, 2026 14:40
@tinder-maxwellelliott tinder-maxwellelliott marked this pull request as ready for review July 6, 2026 15:01
@tinder-maxwellelliott tinder-maxwellelliott merged commit 7a42b92 into master Jul 6, 2026
15 checks passed
@tinder-maxwellelliott tinder-maxwellelliott deleted the claude/objective-proskuriakova-8110ff branch July 6, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: always-affected hashing for targets tagged external (non-hermetic / undeclared workspace reads)

1 participant