Cache Keys

Cache Keys, Restore Keys and Invalidation

A cache entry is an immutable archive filed under a key. setup-node's key joins the platform, the package manager and hashFiles('package-lock.json'), a SHA-256 of the lock file: node-cache-Linux-x64-npm-874b.... Change the lock file and the key changes, so a stale cache is never restored; that is the whole invalidation strategy.

actions/cache (v6.1.0) exposes the mechanism. A throwaway workflow, pushed twice to a branch ci/cache-demo, made each run's key unique and added fallbacks:

actions/cache with restore keys, from cache-demo.ymlYAML
- id: npm-cache
  uses: actions/cache@v6
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.run_number }}
    restore-keys: |
      npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-
      npm-${{ runner.os }}-
Output
Cache not found for input keys: npm-Linux-874b...-1, npm-Linux-874b...-, npm-Linux-
Cache saved with key: npm-Linux-874b14aefae1e1bf214d2bc343513aca820f1d3c80e591a62ae91cbeb98f172
  f-1
...
Cache hit for restore-key: npm-Linux-874b14aefae1e1bf214d2bc343513aca820f1d3c80e591a62ae91cbeb9
  8f172f-1

The second run's exact key missed, so the action tried each restore key as a prefix, restored the newest match and saved a new entry. cache-hit was false: it is true only for an exact match. A run looks in its own branch, then a pull request's base branch, then the default branch; a pull request's caches belong to its merge ref, so main could not reuse those of #17.