Repository navigation
serve: refuse to cache a revision whose query loaded packages its tree does not track - #597
Merged
tinder-maxwellelliott merged 1 commit intoOct 31, 2026
Conversation
…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
marked this pull request as ready for review
October 31, 2026 14:40
tinder-maxwellelliott
deleted the
claude/serve-untracked-package-check
branch
October 31, 2026 14:50
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.
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
.../LibraryTestslabels therefore had to be inside the "to" hashes already, and this PR closes the one bazel-diff-side way that happens.serveswitches revisions withgit checkout --force, which removes files a revision no longer tracks but leaves untracked and ignored files in place. ABUILDfile left behind in the working tree (a generator's leftovers) is loaded bybazel queryat 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 theirbazel query --query_fileaborts withno such package.What changed
Every full generation already lists the revision's tracked paths with
git ls-treeto seed source hashing. The query result is now checked against that index: each main-repository package it returned must have itsBUILDorBUILD.bazelin the tree. Packages below a tracked symlink or a submodule are skipped (ls-treelists neither's contents; Bazel follows both). External labels are not the revision's to track.git clean -fdx, and caches nothing.--allowUntrackedPackagesdemotes it to a warning for workspaces that generateBUILDfiles outside git./metricsreportsuntrackedPackages: {allowed, rejections}.modifiedFilepathspath 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.Tests
untracked_packages_skips_external_labels_symlinks_and_submodules: pure unit test over a hand-writtenls-treelisting (root package, external labels, symlink and submodule ancestors).full_generation_rejects_packages_the_revision_does_not_track: real git plus the fake bazel, withpkg/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 untrackedkeep/StaleTestspackage in thedeleted_packagefixture. The default server answers non-200 naming it and nothing lands in the cache; with--allowUntrackedPackagesthe server reports//keep/StaleTests:stale_testsas 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
servedeployment whose working tree holds generated, gitignoredBUILDfiles 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