From 0eeabf62b01b0a9d4add2a61ed073fe936be10be Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 19 Sep 2026 02:23:53 +0000 Subject: [PATCH] Stop falling back to a `_build` cache from a different mix.lock 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 --- action.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 6c4543d..08c6972 100644 --- a/action.yml +++ b/action.yml @@ -81,13 +81,21 @@ runs: ELIXIR_VERSION: ${{ steps.set-elixir-version.outputs.elixir-version }} OTP_VERSION: ${{ steps.set-otp-version.outputs.otp-version }} + # Deliberately no `restore-keys`: falling back to a `_build` compiled from + # a different mix.lock is unsafe. If a dependency's version moved outside + # its new requirement, Mix reads the stale `.app` file and fails + # `deps.loadpaths` with a hard "does not match the requirement" error + # instead of recompiling, and a job that fails never saves a cache to + # repair itself. On a lock change the first run compiles dependencies from + # scratch; every run after it gets an exact hit. The deps, Hex and Rebar + # caches below keep their fallbacks: `mix deps.get` reconciles those + # against the lock, and a re-fetched dependency is recompiled rather than + # validated against its old build. - name: Restore application build cache uses: actions/cache@v6.1.0 with: path: _build/${{ env.MIX_ENV }} key: build-${{ steps.set-shared-cache-key.outputs.shared-cache-key }}-${{ env.MIX_ENV }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - build-${{ steps.set-shared-cache-key.outputs.shared-cache-key }}-${{ env.MIX_ENV }}- - name: Restore project dependencies cache uses: actions/cache@v6.1.0