Skip to content
Latchkey

GitHub Actions "Cache already exists" - Entries Are Immutable

GitHub Actions cache entries are immutable. Saving the same key again is a no-op (with a warning), so if your cached contents changed but the key did not, the new contents are never stored.

What this error means

A save step warns that the cache already exists and does not upload, leaving a later run to restore stale contents because the key never changed.

Actions log
Warning: Cache save failed: Unable to reserve cache with key
npm-cache, another job may be creating this cache. Reserve cache failed.
# or: a cache entry for the key already exists and was not overwritten

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

Re-saving an existing key

Once a key is populated, that entry is fixed. A second save with the same key does nothing, so updated contents are silently not stored.

Key not derived from the inputs that change

If the cache contents depend on files the key does not hash, the key stays the same while contents drift, and the cache never updates.

How to fix it

Derive the key from what changes

Include a hash of the inputs that affect the cached output so a content change produces a new key.

.github/workflows/ci.yml
- uses: actions/cache@v4
  with:
    path: ~/.cache/build
    key: build-${{ runner.os }}-${{ hashFiles('lock', 'config.toml') }}
    restore-keys: |
      build-${{ runner.os }}-

Roll the key when you must refresh

  1. Bump a version prefix in the key (v2-...) to force a fresh entry.
  2. Use restore-keys so the previous entry still serves a warm start.
  3. Do not expect a re-save of the same key to update the contents.

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

  • Hash every input that changes the cached output into the key.
  • Use a version prefix you can bump to invalidate a cache.
  • Treat cache entries as write-once per key.

Frequently asked questions

What causes GitHub Actions "Cache already exists"?
There are 2 common causes: re-saving an existing key and key not derived from the inputs that change. Once a key is populated, that entry is fixed.
How do I fix GitHub Actions "Cache already exists"?
There are 2 fixes depending on which cause you have: derive the key from what changes and roll the key when you must refresh. Work through them in order, since the first is the most common.
What does GitHub Actions "Cache already exists" actually mean?
A save step warns that the cache already exists and does not upload, leaving a later run to restore stale contents because the key never changed.
How do I stop GitHub Actions "Cache already exists" happening again?
Hash every input that changes the cached output into the key. 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