Skip to content

serve: refuse to cache a revision whose query loaded packages its tree does not track - #597

Merged
tinder-maxwellelliott merged 1 commit into
masterfrom
claude/serve-untracked-package-check
Oct 31, 2026
Merged

tinder-maxwellelliott merged 1 commit into
masterfrom
claude/serve-untracked-package-check

Conversation

@tinder-maxwellelliott

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #591, which pinned that a package deleted between two revisions never comes out of the diff itself. The report's stale .../LibraryTests labels therefore had to be inside the "to" hashes already, and this PR closes the one bazel-diff-side way that happens.

serve switches revisions with git checkout --force, which removes files a revision no longer tracks but leaves untracked and ignored files in place. A BUILD file left behind in the working tree (a generator's leftovers) is loaded by bazel query at every revision, its targets are hashed from disk, and the entry is cached under a revision that does not contain them. Consumers then receive impacted labels for packages their checkout does not have, and their bazel query --query_file aborts with no such package.

What changed

Every full generation already lists the revision's tracked paths with git ls-tree to seed source hashing. The query result is now checked against that index: each main-repository package it returned must have its BUILD or BUILD.bazel in the tree. Packages below a tracked symlink or a submodule are skipped (ls-tree lists neither's contents; Bazel follows both). External labels are not the revision's to track.

  • A mismatch fails the request, names the packages (ten, then a count), suggests git clean -fdx, and caches nothing.
  • --allowUntrackedPackages demotes it to a warning for workspaces that generate BUILD files outside git.
  • /metrics reports untrackedPackages: {allowed, rejections}.
  • The scoped modifiedFilepaths path has no tree index and is unchanged. Partial-graph derivations read only the git tree, so this also protects the derived-vs-full byte-identical invariant from a stale working tree.
  • README section and help text regenerated.

Tests

  • untracked_packages_skips_external_labels_symlinks_and_submodules: pure unit test over a hand-written ls-tree listing (root package, external labels, symlink and submodule ancestors).
  • full_generation_rejects_packages_the_revision_does_not_track: real git plus the fake bazel, with pkg/ gitignored. Strict fails naming //pkg, caches nothing, counts one rejection; lenient serves //pkg:lib.
  • regressions::serve_rejects_packages_the_revision_does_not_track: plants an untracked keep/StaleTests package in the deleted_package fixture. The default server answers non-200 naming it and nothing lands in the cache; with --allowUntrackedPackages the server reports //keep/StaleTests:stale_tests as impacted, which is exactly the report's symptom.

These plus the existing serve unit and e2e cases pass locally under Bazel 8.8.2 on top of current master.

Rollout note

This is default-on. A serve deployment whose working tree holds generated, gitignored BUILD files will start failing generations with a message naming the packages. That is the intended loud failure; such a deployment should either clean the tree per revision or start with --allowUntrackedPackages, accepting that its hashes then describe the working tree rather than the revision.

🤖 Generated with Claude Code

…e does not track

`serve` switches revisions with `git checkout --force`, which removes the
files a revision no longer tracks but leaves untracked and ignored files in
place. A `BUILD` file left behind in the working tree (a generator's
leftovers) is therefore loaded by `bazel query` at every revision, its
targets are hashed from disk, and the entry is cached under a revision that
does not contain them. Consumers then receive impacted labels for packages
their checkout does not have, which is the failure the deleted-package
report describes: `bazel query --query_file` over the labels aborts with
`no such package` and the whole result is lost.

Every full generation already lists the revision's tracked paths with
`git ls-tree` to seed source hashing. Now the query result is checked
against that index: each main-repository package it returned must have its
`BUILD` or `BUILD.bazel` in the tree. Packages below a tracked symlink or a
submodule are skipped, since `ls-tree` lists neither's contents and Bazel
follows both. A mismatch fails the request, names the packages (ten, then a
count), suggests `git clean -fdx`, and caches nothing. The scoped
`modifiedFilepaths` path has no tree index and is unchanged.

`--allowUntrackedPackages` demotes the failure to a warning for workspaces
that generate `BUILD` files outside git. `/metrics` reports the flag and a
rejection counter under `untrackedPackages`.

Tests: a pure unit test over a hand-written `ls-tree` listing (external
labels, symlink and submodule ancestors, root package), a real-git fake-bazel
unit test where `pkg/` is gitignored (strict fails and caches nothing,
lenient serves `//pkg:lib`), and the e2e case
`serve_rejects_packages_the_revision_does_not_track`, which plants an
untracked `keep/StaleTests` package in the `deleted_package` fixture: the
default server answers non-200 naming it, the opted-out server reports
`//keep/StaleTests:stale_tests` as impacted, the report's symptom.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tinder-maxwellelliott
tinder-maxwellelliott marked this pull request as ready for review October 31, 2026 14:40
@tinder-maxwellelliott
tinder-maxwellelliott merged commit a692932 into master Oct 31, 2026
19 checks passed
@tinder-maxwellelliott
tinder-maxwellelliott deleted the claude/serve-untracked-package-check branch October 31, 2026 14:50
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.

1 participant