GitHub Actions Cache restore-keys Restores a Stale Partial Match
A cache restore reports a hit but uses an old archive, because the exact key missed and a broad restore-keys prefix fell back to a stale entry. The job runs with outdated cached content instead of rebuilding fresh.
What this error means
The cache step reports a partial (restore-keys) hit and the job proceeds with stale dependencies - a lockfile change did not invalidate the cache because a loose prefix matched an older entry.
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm- # too broad - falls back to ANY old npm cacheCommon causes
restore-keys prefix is too broad
restore-keys are prefix fallbacks used when the exact key misses. A very loose prefix matches an old archive, so the job silently reuses stale content.
No reconcile step after a partial restore
Tools must still reconcile after a partial restore (e.g. npm ci, not just relying on node_modules). Skipping that leaves the stale cache in effect.
How to fix it
Scope restore-keys and reconcile
Use a more specific restore-keys prefix and always run the install so the partial cache is updated to match the lockfile.
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
- run: npm ci # reconcile against the lockfile after a partial restoreMake the exact key carry the real inputs
- Hash the lockfile (or full dependency set) into the exact key so changes force a fresh save.
- Keep restore-keys specific enough to only match compatible prior caches.
- Always run the dependency install after restore so a partial hit is brought up to date.
How to prevent it
- Keep restore-keys prefixes specific (include OS and tool).
- Always reconcile (install) after a partial cache restore.
- Hash the real dependency inputs into the exact cache key.