feat: add --alwaysAffectedTags to always impact non-hermetic tagged targets (#401)#410
Merged
tinder-maxwellelliott merged 2 commits intoJul 6, 2026
Merged
Conversation
…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>
200c8f4 to
7b9cbf2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Closes #401.
bazel-diffhashes 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 asbuildifier_test,gofmt_test, oreslint_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-filepathsover-invalidates the whole graph;--excludeExternalTargets/--fineGrainedHashExternalReposare about external repos, not the tag;--cqueryExpressiononly sees declared deps).Change
New
generate-hashesflag:--alwaysAffectedTags(comma-separated, e.g.--alwaysAffectedTags=external).Any rule whose
tagsattribute 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.How it works
BazelRule.tags()reads thetagsSTRING_LISTattribute from the query/cquery proto.RuleHashermixes the sentinel into the tagged rule's direct digest, so it is classifiedDIRECT-impacted for build-graph distance metrics. It also bubbles into the overall digest, so any rdeps are conservatively re-hashed too.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
generate-hasheshas 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.generate-hashes;serve/get-impacted-targetspass the (defaulted-empty) parameter unchanged.Tests
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
generate-hashes --helpblock.🤖 Generated with Claude Code