Repository navigation
Stop falling back to a _build cache from a different mix.lock - #13
Merged
Merged
Conversation
The `_build` cache had a `restore-keys` prefix without the lock hash, so a run whose exact key missed restored the most recent `_build` compiled from some other mix.lock. That is unsafe: when a dependency's version has moved outside its new requirement, Mix reads the stale `.app` file and fails `mix deps.loadpaths` with a hard "does not match the requirement" error rather than recompiling, and because `actions/cache` only saves on success the job can never repair its own namespace. sensetra hit exactly this after a leto `~> 0.1` -> `~> 0.3` bump: every PR with an earlier green Lint run fell back to its own pre-bump `_build/dev` and failed on `leto ... got "0.2.0"` until the caches were purged by hand. Drop the fallback for `_build` only. A lock change now compiles dependencies from scratch once per namespace; every run after it gets an exact hit. The deps, Hex and Rebar caches keep their fallbacks, which are safe because `mix deps.get` reconciles them against the lock and a re-fetched dependency is recompiled rather than validated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Problem
The
_buildcache step has arestore-keysprefix that omits themix.lockhash. When the exact key misses,actions/cacherestores the most recently created_buildin the namespace — compiled from some othermix.lock.That is unsafe. If a dependency's version has moved outside its new requirement, Mix reads the stale
.appfile in_buildandmix deps.loadpathsfails hard rather than recompiling:And because
actions/cachesaves in apost-if: success()step, a job that fails this way never saves a good_buildunder the new key — so it falls back to the same stale entry next run, forever.sensetra hit this on 2026-09-18/19 after leto went
~> 0.1→~> 0.3onmain. Caches are visible only to the ref that created them plus the default branch, and the Lint workflow never runs onmain, so every PR with an earlier green Lint run fell back to its own pre-bump_build/devand failed on every push until the caches were purged by hand. Guides on the same commits passed only becauseci.ymlhad beenworkflow_dispatched onmainafter the bump, seeding a main-scoped entry for its namespace.Consumers previously avoided this by passing
cache-key: v1-${{ hashFiles('mix.lock') }}, which put the lock hash into the shared prefix and made the fallback lock-specific. That was removed in a cleanup on 2026-09-16 because it looked redundant with the primary key — the fallback was the part it was protecting.Change
Remove
restore-keysfrom the_buildcache only, with a comment explaining why so it isn't re-added as tidy-up._buildis restored on an exactmix.lockmatch, otherwise compiled from scratch. On a lock change that is one cold dependency compile per namespace and scope; every run after it gets an exact hit. In sensetra a fully cold Lint run (deps fetch + deps compile + lint + app compile) was about 3½ minutes on a 4-vCPU runner.deps/,~/.hexand~/.cache/rebar3keep their fallbacks. Those are safe:mix deps.getreconcilesdeps/against the lock and a re-fetched dependency is recompiled rather than validated against its old build, and the other two are download caches.This is the same trade CargoSense's CircleCI config makes — every restore key there ends in
{{ checksum "mix.lock" }}, with no cross-lock fallback.Release
v1currently points at thev1.6.0commit. After merge this needs av1.7.0tag andv1moved to it for consumers on@v1to pick it up.Related
cache-keytov2as a workaround; it becomes unnecessary oncev1carries this change.🤖 Generated with Claude Code