Problem
Fallout re-runs every target in the execution plan on every invocation. There is no target-level cache. MSBuild's own incremental build is timestamp-based, works only inside one workspace, and does not survive a fresh CI runner. Gradle, Bazel and Nx all cache at this level; NUKE never did.
A full rebuild on every iteration is the main limit on how many edit-build-check cycles fit into a working session. That applies to a person at a keyboard and to an automated caller equally.
Outcome
A target declares its inputs and outputs. Fallout hashes the inputs, and when the hash matches a stored entry it restores the outputs and skips the target.
Acceptance criteria
Making it work on CI, and on GitHub Actions in particular
This has to survive ephemeral runners, which is where most of the design risk sits.
- Absolute paths, timestamps and machine identity all differ between runs. Hash content, keep paths relative inside the cache key, and require deterministic build settings (
ContinuousIntegrationBuild, Deterministic, PathMap) so outputs can move between machines.
actions/cache stores one immutable archive per key and cannot update an existing key. A first pass can restore by key prefix and save under a new key at the end of the job. Fallout's workflows already cache .fallout/temp, so a cache directory there needs no new workflow step.
- Entry-level caching needs the runner's cache service API (
ACTIONS_RESULTS_URL and ACTIONS_RUNTIME_TOKEN), which supports per-entry read and write. This is how sccache, Gradle and Turborepo do remote caching on GitHub Actions. It is the argument for a pluggable cache store rather than a hard dependency on actions/cache.
- The cache limit is 10 GB per repository, evicted least-recently-used. Per-entry eviction starts to matter once build outputs are stored.
- Pull requests from forks receive a read-only cache token, so an untrusted pull request cannot write cache entries.
- Adjacent and independent of this work: the cache key in
.github/workflows/build.yml has no restore-keys, so editing any .csproj discards the whole NuGet package cache.
Notes
A wrong cache hit is worse than no cache, so caching stays opt-in per target until the key provably covers everything that affects output. Suggested order: local hashing and cache first, pluggable store second, GitHub Actions cache-service backend third.
Problem
Fallout re-runs every target in the execution plan on every invocation. There is no target-level cache. MSBuild's own incremental build is timestamp-based, works only inside one workspace, and does not survive a fresh CI runner. Gradle, Bazel and Nx all cache at this level; NUKE never did.
A full rebuild on every iteration is the main limit on how many edit-build-check cycles fit into a working session. That applies to a person at a keyboard and to an automated caller equally.
Outcome
A target declares its inputs and outputs. Fallout hashes the inputs, and when the hash matches a stored entry it restores the outputs and skips the target.
Acceptance criteria
global.json, the configuration, the target framework or RID, the Fallout version, and every parameter the target reads--no-cachebypasses the cache entirelyMaking it work on CI, and on GitHub Actions in particular
This has to survive ephemeral runners, which is where most of the design risk sits.
ContinuousIntegrationBuild,Deterministic,PathMap) so outputs can move between machines.actions/cachestores one immutable archive per key and cannot update an existing key. A first pass can restore by key prefix and save under a new key at the end of the job. Fallout's workflows already cache.fallout/temp, so a cache directory there needs no new workflow step.ACTIONS_RESULTS_URLandACTIONS_RUNTIME_TOKEN), which supports per-entry read and write. This is how sccache, Gradle and Turborepo do remote caching on GitHub Actions. It is the argument for a pluggable cache store rather than a hard dependency onactions/cache..github/workflows/build.ymlhas norestore-keys, so editing any.csprojdiscards the whole NuGet package cache.Notes
A wrong cache hit is worse than no cache, so caching stays opt-in per target until the key provably covers everything that affects output. Suggested order: local hashing and cache first, pluggable store second, GitHub Actions cache-service backend third.