GitHub Actions cache/save and cache/restore Split Used Incorrectly
Splitting actions/cache into separate restore and save steps gives you control, but a mismatched key, or saving even after a hit, leads to perpetual misses or redundant uploads.
What this error means
The cache never restores despite a prior save, or every run re-uploads the cache, because the restore and save steps disagree on the key or the save runs unconditionally.
- uses: actions/cache/restore@v4
id: cache
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
# ... build ...
- uses: actions/cache/save@v4
with:
path: ~/.npm
key: npm-${{ github.run_id }} # different key - restore can never find itCommon causes
Restore and save keys differ
The save key must be the same key the restore step looks up. Using a run-id or other changing value on save means restore in the next run never matches it.
Saving after an exact hit
If restore reported an exact key hit, re-saving the same key fails or wastes time. Save should be gated on a miss.
How to fix it
Use the same key and gate the save
- uses: actions/cache/restore@v4
id: cache
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
- run: npm ci
- uses: actions/cache/save@v4
if: steps.cache.outputs.cache-hit != 'true'
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}Prefer the combined action when you can
- Use plain actions/cache@v4 unless you specifically need the split.
- Reach for restore/save only to save mid-job or under custom conditions.
- Keep the restore key and the save key identical.
How to prevent it
- Match restore and save keys exactly.
- Gate the save step on a cache miss.
- Default to the combined actions/cache action unless a split is required.