Skip to content
Latchkey

GitHub Actions cache key collision / immutable cache not updating

Cache entries in GitHub Actions are immutable: once a key is saved it cannot be overwritten, so reusing a static key while the underlying content changes leaves you restoring stale data forever.

What this error means

Builds keep restoring outdated dependencies or artifacts even though inputs changed, and the save step reports the key already exists instead of updating it.

github-actions
Cache hit on key: deps-cache (stale)
Cache already exists, not overwriting immutable entry.

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

Static key that never changes

A constant key (deps-cache) is restored on every run and can never be updated, so the first saved content sticks.

Key not tied to content hash

The key does not include a hash of the inputs (lockfile), so changed inputs map to the same immutable entry.

How to fix it

Make the key content-addressed

  1. Include hashFiles of the lockfile or inputs in the primary key.
  2. Add restore-keys prefixes for partial reuse across changes.
  3. Let a new hash produce a new entry instead of overwriting.
.github/workflows/ci.yml
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
  ${{ runner.os }}-deps-

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

  • Always derive cache keys from a content hash.
  • Never reuse a static key for changing content.
  • Use restore-keys for graceful partial hits.

Frequently asked questions

What causes GitHub Actions cache key collision / immutable cache not updating?
There are 2 common causes: static key that never changes and key not tied to content hash. A constant key (deps-cache) is restored on every run and can never be updated, so the first saved content sticks.
How do I fix GitHub Actions cache key collision / immutable cache not updating?
Make the key content-addressed. Include hashFiles of the lockfile or inputs in the primary key.
What does GitHub Actions cache key collision / immutable cache not updating actually mean?
Builds keep restoring outdated dependencies or artifacts even though inputs changed, and the save step reports the key already exists instead of updating it.
How do I stop GitHub Actions cache key collision / immutable cache not updating happening again?
Always derive cache keys from a content hash. 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