Repository navigation
Description
Why this exists
#112 (FastLED examples CI benchmark) landed iteration 3 and measured a warm-cache run that produced zero cross-run compile speedup despite actions/cache correctly restoring 304 MB of fbuild + project state. Every one of the 83 FastLED examples re-compiled at the same rate as cold (~1.5s per example × 82 ≈ 124s of avoidable work).
Root cause is not one bug but a cluster of cache-key and cache-scope bugs, each of which independently breaks cross-run reuse:
- fbuild's fast-path fingerprint mixes mtime into the hash → tar-extract invalidates (build: per-TU cache misses across CI runs — fast-path fingerprint bakes mtime, defeats content-based reuse #146).
- fbuild bakes absolute filesystem paths into cache keys → cross-workspace / cross-runner miss (build: absolute filesystem paths baked into cache keys — breaks cross-runner / cross-workspace reuse #148).
- zccache (the per-TU object store) isn't discoverable by consumer workflows → can't be cached at all (daemon/setup: expose zccache store location so consumer workflows can actions/cache it #149).
- zccache's own keys may bake absolute paths (the command line passed to it) → needs audit (build: audit zccache per-TU cache keys for cross-runner stability #150).
- The benchmark workflow doesn't include zccache's store in its
actions/cachepath list yet (bench: add zccache store to bench/fastled-examples actions/cache path list #151). - No consumer-facing doc explains the full caching recipe (docs: document cross-run CI cache strategy for fbuild consumers #152).
Fix all of them and the cross-run warm regime moves from "caches 304 MB and saves 9 seconds" to "caches ~500 MB and saves ~2 minutes."
Evidence
Benchmark on bench/fastled-examples, board uno, fbuild 2.1.21, Ubuntu 24.04:
| run | cache-hit | compile | first example | examples 2–83 |
|---|---|---|---|---|
| iter3 cold 24626421386 | false | 140s | 20.5s | 1.4–2.0s |
| iter3 warm 24626590831 | true (304 MB restored) | 142s | 11.8s | 1.4–2.0s |
The 9s saved on example #1 is the ~/.fbuild toolchain-materialization cache paying off. Per-TU reuse across runs: zero.
Within a single run, the fbuild daemon is already warm after example #1 (examples 2–83 run at ~1.5s each). That "warm-within-run" rate is what we want to reproduce cross-run. Nothing in this meta is about speeding up that rate further — we just want to stop throwing it away between runs.
Sub-issues
fbuild code changes
- build: per-TU cache misses across CI runs — fast-path fingerprint bakes mtime, defeats content-based reuse #146 — Fast-path fingerprint bakes mtime; add blake3 content-hash fallback in
FileStamp. (filed, TDD plan included) - build: absolute filesystem paths baked into cache keys — breaks cross-runner / cross-workspace reuse #148 — Absolute paths in cache keys:
compiler.rs:303(toolchain path in rebuild signature) andbuild_fingerprint/mod.rs:106,127,236(absolute watch-set paths in fingerprint). Fix by hashing compiler identity and paths relative to watch root. (filed, TDD plan included)
Setup / distribution
- daemon/setup: expose zccache store location so consumer workflows can actions/cache it #149 —
setupcomposite action must expose zccache's store location (zccache-store-pathstep output +ZCCACHE_DIRenv var) so consumer workflows can feed it toactions/cache. (filed) - build: audit zccache per-TU cache keys for cross-runner stability #150 — Audit zccache's own per-TU cache keys for cross-runner stability; if it bakes absolute source / include / output paths from the wrapped command, file upstream and/or normalize at
wrap_argscall site. (filed, research-first)
Benchmark + validation
- bench: add zccache store to bench/fastled-examples actions/cache path list #151 — Once daemon/setup: expose zccache store location so consumer workflows can actions/cache it #149 lands, add
zccache-store-pathtobench/fastled-examplesworkflow'sactions/cachepath list. Bump cache version, re-run iter4 cold + warm. (filed, blocked by daemon/setup: expose zccache store location so consumer workflows can actions/cache it #149)
Documentation
- docs: document cross-run CI cache strategy for fbuild consumers #152 —
docs/CI_CACHE.md: directories to cache, key composition, invalidation pattern, expected numbers from [META] Fastest possible FastLED examples CI rebuild — profile + benchmark #112 final iter. (filed, sequenced last)
Ordering / critical path
#146 ─┬──────┐
#148 ─┘ │
├─► validate via #151 ─► #152 docs
#149 ─► #150 ─┘
- build: per-TU cache misses across CI runs — fast-path fingerprint bakes mtime, defeats content-based reuse #146 + build: absolute filesystem paths baked into cache keys — breaks cross-runner / cross-workspace reuse #148 are independent and can land in either order or in parallel. They are the fbuild-side cache-key fixes.
- daemon/setup: expose zccache store location so consumer workflows can actions/cache it #149 → build: audit zccache per-TU cache keys for cross-runner stability #150 → bench: add zccache store to bench/fastled-examples actions/cache path list #151 is the zccache chain. build: audit zccache per-TU cache keys for cross-runner stability #150 is research-first: its outcome determines whether bench: add zccache store to bench/fastled-examples actions/cache path list #151 is a trivial path-list addition or needs a workaround.
- docs: document cross-run CI cache strategy for fbuild consumers #152 sequences last — wants real numbers from iter4 warm.
The biggest single lever is #146. On its own it should recover most of the ~124s currently spent recompiling unchanged TUs (assuming zccache's own keying is content-based, which #150 will confirm). #148 protects against regressions when the runner image or workspace path shifts. #149 + #151 turn the warm number from "cheap per-TU reuse because daemon is warm within-run" into "zero compile work because the prior objects are on disk already."
Definition of done
On the bench/fastled-examples benchmark (board=uno, examples=all, FastLED master, fbuild ≥ release-containing-#146-and-#148):
- Same-runner warm regime: a warm run after a cold run on the same runner image drops compile phase from ~142s to < 10s (only the first example pays a measurable cost; examples 2–83 finish sub-second each).
- Cross-runner warm regime: a warm run on a different runner of the same image (i.e., workspace paths differ, toolchain paths differ, but content is identical) also hits the cache and completes in the same range. This is what build: absolute filesystem paths baked into cache keys — breaks cross-runner / cross-workspace reuse #148 and build: audit zccache per-TU cache keys for cross-runner stability #150 exist to enable.
- Numbers published as a final iteration comment on [META] Fastest possible FastLED examples CI rebuild — profile + benchmark #112.
docs/CI_CACHE.mdin tree describing the full recipe.
Until (1)+(2) hold, #112 cannot measurably improve beyond its current iter3 numbers, because the compile phase dominates total wall-clock and nothing else we could optimize (parallel, venv cache, uv sync) adds up to the 124s of avoidable recompile.
Non-goals
- Parallelism (
--parallel, concurrent per-example dispatch). Orthogonal to cache resilience; tracked on [META] Fastest possible FastLED examples CI rebuild — profile + benchmark #112 directly as a separate iteration lever. Parallelism hides per-TU inefficiency but does not fix it. - Toolchain download speedup. Already bounded by
~/.fbuildcache which works correctly. - FastLED
.venvcaching (23suv syncphase). Orthogonal; tracked on [META] Fastest possible FastLED examples CI rebuild — profile + benchmark #112. - zccache daemon pre-spawn warm-up. Performance lever; separate follow-up.
Related
- [META] Fastest possible FastLED examples CI rebuild — profile + benchmark #112 — FastLED examples CI benchmark (primary consumer).
- perf: watch-set cache hit/miss telemetry for validating savings in the field #123, perf(daemon): watch-set cache hit/miss telemetry #127, perf(build): add fast-path fingerprint check to AVR orchestrator #128 — recent watch-set fingerprint perf work. Same code region as build: per-TU cache misses across CI runs — fast-path fingerprint bakes mtime, defeats content-based reuse #146/build: absolute filesystem paths baked into cache keys — breaks cross-runner / cross-workspace reuse #148; new
content_hashfield should wire through those paths cleanly. - fix(build): P0 regression — Operation not permitted (os error 1) on warm build #134, fix(ci): preserve exec bit on fbuild console script in wheel #135 — recent cache-correctness fixes that unblocked iter3 from running at all.
Activity
build: stabilize cache keys across CI restores
zackees commentedon May 11, 2026
DoD evidence
Both halves of #147's Definition of Done now have committed evidence:
bench-fastled-examples) is two orders of magnitude tighter than the original "<10 s warm" bar. Any future regression that pushes warm rebuilds back toward the iter3 baseline will trip the bench gate.crates/fbuild-build/tests/cache_survives_tar_extract.rs, which exercises the original failure mode end-to-end via the public hash APIs:fingerprint_survives_tar_roundtrippins the watch-set hash as content-derived (no mtime).fingerprint_survives_workspace_relocationpinsrelative_path_for_hashagainst absolute-path leakage.compiler_signature_survives_toolchain_path_changepins thecompiler_identitysubstitution atcompiler.rs:307against compiler-path leakage.If a future change re-introduces mtime into the watch hash or bakes an absolute path into
build_fingerprint.json, the per-layer unit tests would still pass but this new suite turns RED with a clear "the fingerprint changed when it should not have" signal — instead of the bench gate noticing only as a "slow" failure on the next AC run.Closing as completed. Tracked in #231; PR #232.
test(build): #231 hermetic regression gate for #147 cache-survives-ta…
test(build): #231 follow-up -- AVR Uno tar-extract e2e regression gate (