Skip to content
Latchkey

GitHub Actions Cache Not Saved After an Exact-Key Hit

actions/cache skips the save when an entry already exists for the exact key, because cache entries are immutable. If the content changed but the key did not, the new content is never stored.

What this error means

The post-job cache step logs that the cache already exists and is not saved, so updated dependencies are never persisted under that key and later runs restore stale content.

Actions log
Cache hit occurred on the primary key npm-abc, not saving cache.
# updated node_modules are not persisted because the key did not change

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

Immutable cache keys

A cache key maps to one immutable entry. Once saved, the same key is never overwritten, so changed content under an unchanged key is lost.

Key not reflecting content changes

If the key does not include a hash of the inputs that actually changed, the new state cannot get its own entry.

How to fix it

Make the key reflect the content

Include a lockfile hash so a dependency change produces a new key and a fresh save, with restore-keys for fallback.

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

Use a rolling key when content changes often

  1. Add a build-cache style key that includes a date or counter when you genuinely want frequent refreshes.
  2. Rely on restore-keys to load the most recent prior entry.
  3. Avoid manual cache deletion as a workaround; fix the key instead.

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

  • Tie the cache key to a hash of the content it stores.
  • Use restore-keys so a new key still restores a recent cache.
  • Do not expect the same key to update; design keys to roll forward.

Frequently asked questions

What causes GitHub Actions cache not saved after an Exact-Key hit?
There are 2 common causes: immutable cache keys and key not reflecting content changes. A cache key maps to one immutable entry.
How do I fix GitHub Actions cache not saved after an Exact-Key hit?
There are 2 fixes depending on which cause you have: make the key reflect the content and use a rolling key when content changes often. Work through them in order, since the first is the most common.
What does GitHub Actions cache not saved after an Exact-Key hit actually mean?
The post-job cache step logs that the cache already exists and is not saved, so updated dependencies are never persisted under that key and later runs restore stale content.
How do I stop GitHub Actions cache not saved after an Exact-Key hit happening again?
Tie the cache key to a hash of the content it stores. 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