Skip to content
Latchkey

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.

.github/workflows/ci.yml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-          # too broad - falls back to ANY old npm cache

Diagnose it: was the cache hit, and was it the right one?

Cache bugs split into three shapes and they need different fixes: the cache never saved, it saved but the key never matches on restore, or it restored a stale entry through a restore-keys prefix and is now poisoning the build. The step output tells you which one you have.

.github/workflows/ci.yml
- uses: actions/cache@v4
  id: cache
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-npm-

- name: What happened
  run: |
    echo "exact hit: ${{ steps.cache.outputs.cache-hit }}"
    echo "key used:  ${{ steps.cache.outputs.cache-matched-key }}"

Common 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.

.github/workflows/ci.yml
- 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 restore

Make the exact key carry the real inputs

  1. Hash the lockfile (or full dependency set) into the exact key so changes force a fresh save.
  2. Keep restore-keys specific enough to only match compatible prior caches.
  3. Always run the dependency install after restore so a partial hit is brought up to date.

Cache limits that produce confusing failures

  • Repository cache is capped at 10 GB. Past that, GitHub evicts least-recently-used entries, so a large cache can silently stop persisting.
  • Caches are scoped by branch. A cache written on a feature branch is not visible to another feature branch, only to its base and its own descendants.
  • An entry not read for 7 days is evicted, so a rarely-run workflow effectively never has a warm cache.
  • Restoring a cache built for a different tool version is worse than a cold start, because you get a corrupted tree instead of a clean install. Always include the tool version in the key.

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.

Frequently asked questions

What causes GitHub Actions cache restore-keys restores a stale partial match?
There are 2 common causes: restore-keys prefix is too broad and no reconcile step after a partial restore. restore-keys are prefix fallbacks used when the exact key misses.
How do I fix GitHub Actions cache restore-keys restores a stale partial match?
There are 2 fixes depending on which cause you have: scope restore-keys and reconcile and make the exact key carry the real inputs. Work through them in order, since the first is the most common.
What does GitHub Actions cache restore-keys restores a stale partial match actually mean?
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.
How do I stop GitHub Actions cache restore-keys restores a stale partial match happening again?
Keep restore-keys prefixes specific (include OS and tool). The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card