Repository navigation
ci(nix): key the Bazel-Dev lane to the hermetic toolchain identity so it can hit the warm cache - #2874
Merged
Conversation
…oolchain identity The Nix / Bazel Dev lane gets 0/2723 remote cache hits. It compiles inside `nix develop`, where the default C++/Rust toolchain resolves to nix-store paths (/nix/store/<hash>-clang|gcc|rust) that are baked into every action key, so its digests never match the cache the Native lanes warm off a non-nix toolchain (the Bazel 9 Native lane gets 2273/3915 against the same endpoint). If the remote cache warms one Bazel config it should warm both; this closes that gap by keying the lane on a stable, relocatable toolchain identity rather than host paths. - Factor the hermetic LLVM platform + downloaded Rust toolchains that nl-rbe already uses into a new, endpoint-free `nl-toolchain` config, and have nl-rbe inherit it via `--config=nl-toolchain`. These toolchains key on downloaded CONTENT, not host paths, so they produce identical action digests on a bare runner, inside `nix develop`, or on a remote worker. No behavior change for nl-rbe (same flags, now shared). - Apply `--config=nl-toolchain` to the Bazel Dev lane. This is cache-read only (no remote executor added): the lane keeps running locally, it just keys its compiles against the hermetic identity so they can hit a warm cache. Verification: - PROVEN (measured): action keys are byte-identical. A `bazel aquery` proof run compares three ways and shows the nix+nl-toolchain command lines match the bare-runner nl-toolchain command lines and no longer reference /nix/store: https://github.com/b7r6/nativelink/actions/runs/37059855343 - PROJECTED (not yet measured): with a warm cache the lane should drop from ~670s to ~250s. This is a projection -- proven here is key alignment, not a full warm read. - REQUIRES ONE UPSTREAM CHANGE to realize the win: the trusted main-push cache warmer must also build with `--config=nl-toolchain`, so it warms the same key this lane now requests (today's Native warmer uses bare `--extra_toolchains` without the `@llvm` platform pin). One line; happy to do it in a follow-up once this lands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b7r6
added a commit
to b7r6/nativelink
that referenced
this pull request
Oct 3, 2026
…y (--config=nl-toolchain) Realizes the warm-cache contract TraceMachina#2874 sets up. That change keys the Bazel Dev (nix) lane's reads on the endpoint-free nl-toolchain config — the hermetic LLVM platform + downloaded Rust toolchains that nl-rbe already uses — because toolchains keyed on downloaded content produce identical action digests on a bare runner, inside nix develop, or on a remote worker. But nobody writes that digest space yet: the trusted main-push Bazel 9 lane (the only holder of a write key) warms with bare --extra_toolchains, host-toolchain keys the Dev lane can never hit. Switch the Bazel 9 lane from --extra_toolchains=@rust_toolchains//:all to --config=nl-toolchain (which contains that same line plus the @llvm platform pin). On main pushes this lane becomes the warmer for the hermetic digest space; on PRs it reads from it. The Bazel 8 lane is untouched (separate, lockfile_mode=off digest space). Transition cost, stated honestly: the first runs after this merges are cold for everyone until the first main push warms the new key space — one build. Measured so far is key ALIGNMENT, not the warm win: the aquery proof run shows nix+nl-toolchain command lines byte-identical to bare-runner nl-toolchain with no /nix/store references (https://github.com/b7r6/nativelink/actions/runs/37059855343). The warm hit-rate itself cannot be measured from a fork (no write credentials); projected from the Bazel 9 lane's current warm behavior, the Dev lane drops from ~11 min to roughly the Native lane's ~5 min. Stacked on TraceMachina#2874 (requires its nl-toolchain config). Builds on the remote-cache lanes and @palfrey's TraceMachina#2864 disk-cache handling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Oct 7, 2026
Closed
This branch was successfully deployed
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.
What and why
The common case first. The vast majority of PRs change a small dependency cone (a handful of crates). In that regime, with a warm remote cache, the Bazel lanes are fast because their actions are cacheable — but the Nix / Bazel Dev lane can't participate: it lands 0 remote cache hits of its 2723 actions (
2723 processes: 1142 internal, 1700 processwrapper-sandbox— every compile runs locally), so it pays a full from-source build (~12m) on every PR regardless of cone size. By contrast the Bazel 9 Native lane, against the same cache endpoint, gets 1877 remote cache hits + 803 disk cache hits of 3915 actions (3915 processes: 803 disk cache hit, 1877 remote cache hit, 1351 internal, 3 processwrapper-sandbox). This change targets the cache-key mismatch that keeps the Bazel Dev lane cold so it can read the same warm cache.Why the lane misses on keys: it compiles inside
nix develop, where the default C++/Rust toolchain resolves to nix-store paths (/nix/store/<hash>-clang|gcc|rust) that are baked into every action key — so its digests cannot match a cache warmed off a non-nix toolchain. The fix keys the lane on a stable, relocatable toolchain identity instead of host paths. (On runs where the lane also hitsRemote Cache: UNAVAILABLEtransient connectivity, hits are zero for that separate reason; the key mismatch is the standing cause this change removes, and it is the one demonstrated by the aquery proof below.)The shape of the problem (measured fanout curve)
No-op comment at increasing dependency-cone depth, same read-only staging cache. Every cell is a clickable run. This is the shape that makes the case:
Runs: T1 bazel / cargo · T2 bazel / cargo · T3 bazel / cargo · T4 bazel / cargo
Reading it:
cargoon any platform, so even a 1-crate leaf change pays the full ~12m Windows / ~6m Ubuntu tax. Cargo cannot play this game.(Spread check: the T1 leaf Bazel 9 lane measured 3m02s and 3m25s on two independent runs — tight enough that the curve's shape is not noise.)
So for the common case — warm cache, small-cone PR — this change moves the Bazel Dev lane from a flat ~12m into the fast cacheable regime.
The change
Two files, minimal and self-contained:
ci.bazelrc: factor the hermetic LLVM platform + downloaded Rust toolchains thatnl-rbealready uses into a new, endpoint-freenl-toolchainconfig;nl-rbeinherits it via--config=nl-toolchain(no behavior change fornl-rbe— same flags, now shared). These toolchains key on downloaded content, not host paths, so they produce identical action digests on a bare runner, insidenix develop, or on a remote worker..github/workflows/nix.yaml: apply--config=nl-toolchainto the Bazel Dev lane. Cache-read only — no remote executor is added; the lane keeps running locally, it just keys its compiles against the hermetic identity so they can hit a warm cache.How was this verified?
DEMONSTRATED (measured, linkable): the toolchain path signatures align. A
bazel aqueryproof compares the compile action command lines three ways — (A) insidenix developwith the default toolchain, (B) insidenix developwith--config=nl-toolchain, (C) on a bare runner with--config=nl-toolchain— and shows B matches C (the warmer identity) and no longer references/nix/storetoolchains, whereas A does: https://github.com/b7r6/nativelink/actions/runs/37059855343 . That path-signature alignment is the precondition for a warm read. (The proof prints the signatures/counts for inspection rather than hard-failing the job on mismatch; the comparison is in its uploadedaquery-*artifacts.)Green + the key-match is live, not just theoretical (measured, linkable): on the fork verification run the Bazel Dev lane builds successfully with
--config=nl-toolchainand its remote-cache hit count moves from 0 to 18 (3915 processes: 18 remote cache hit, 1351 internal, 2665 processwrapper-sandbox) — Bazel Dev job, full Nix run, all lanes green. The 18 hits are whatever already sits in the staging cache under the hermetic identity; it is small precisely because nothing has warmed the Bazel-Dev cone under this key yet (see the dependency below). That the number is nonzero at all is the direct, non-aquery confirmation that the lane now keys against the warm identity.PROJECTED (NOT measured end-to-end), with its basis stated: once a writer warms this key, the lane should drop from ~12m (the flat curve above) toward the regime the Native Bazel 9 lane already demonstrates against the same cache endpoint — that lane hits 2680/3915 actions cached (1877 remote + 803 disk, 68%) and finishes in ~5-6m. The projection is "the Bazel Dev lane, once keyed identically, reaches a comparable hit rate," i.e. ~12m → ~4-6m (design target ~250s). It is a projection, not a measurement: see the caveat.
Why it is not measured here (stated plainly): realizing the warm read needs a writer that populates this key, and I verified this fork has no such writer — it carries 0 Actions secrets / 0 variables, so its CI reads the staging cache anonymously and cannot write Bazel-Dev-keyed (
nl-toolchain) artifacts into it, and its main-push warmer is non-functional for the same reason. I could not, at any budget, populate a warmnl-toolchain-keyed cache from this fork. So this PR presents the demonstrated key-match plus the projection anchored to the Native lane's measured 68% warm hit rate, rather than a fork-measured warm number I cannot honestly produce.The one honest caveat (worst case, acknowledged)
The warm hit is not yet demonstrable on this fork, and realizing it needs one upstream line: the trusted main-push cache warmer must also build with
--config=nl-toolchain, so it warms the same key this lane now requests. Today's Native warmer uses bare--extra_toolchainswithout the@llvmplatform pin, so it warms a different key. Until that one-line change lands, this lane's hit rate is unchanged — it does not get slower, it just doesn't yet get faster. I'm happy to send that warmer one-liner as a follow-up the moment this lands, or fold it in here if you'd prefer it in one PR. (The fork's own main-push warmer is broken for unrelated reasons, which is why the end-to-end warm number is projected rather than measured here.)Risk
Low. Cache-read keying change only — no remote executor, no cache-write change, no
ci-modechange. Thenl-toolchainextraction is a pure refactor of flagsnl-rbealready carried. Worst case if the warmer line never lands: the Bazel Dev lane's hit rate is unchanged (never slower). macOS lanes do not apply this config (LRE auto-applies conflicting platforms there, already guarded by the setup action).AI assistance
An agent (Claude Opus 4.8) drafted the config refactor, ran the aquery proof, the fanout-curve measurement, and the fork verification, and wrote this description; a human (b7r6) directed it, reviewed every line, and is answerable for all of it. The commit carries a Co-Authored-By trailer.