GitHub Actions cache/restore Never Saves - Missing cache/save Step
A cache never gets populated because the workflow uses actions/cache/restore on its own. The split restore/save actions do not auto-save at job end - only the combined actions/cache does - so you must add a cache/save step.
What this error means
Every run is a cache miss because nothing ever saves the cache. The restore step runs but no corresponding save persists the directory after the build.
# restore only - cache is never written back
- uses: actions/cache/restore@v4
with: { path: ~/.npm, key: npm-${{ hashFiles('package-lock.json') }} }
- run: npm ci
# (no actions/cache/save step) -> next run misses againCommon causes
Split restore without save
actions/cache/restore only reads. Unlike the combined actions/cache, it has no post step that saves, so without an explicit save the cache is never written.
Assuming auto-save like the combined action
The combined actions/cache saves automatically at job end. The restore/save split trades that convenience for control, so you own the save step.
How to fix it
Add a matching cache/save step
Save with the same key (often gated on a cache miss) after the build produces the directory.
- uses: actions/cache/restore@v4
id: cache
with: { path: ~/.npm, key: npm-${{ hashFiles('package-lock.json') }} }
- run: npm ci
- if: steps.cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with: { path: ~/.npm, key: npm-${{ hashFiles('package-lock.json') }} }Or use the combined action
- Switch to actions/cache when you want automatic restore + save.
- Use restore/save split only when you need to control when the save happens.
- Keep the save key consistent with the restore key.
How to prevent it
- Pair every actions/cache/restore with an actions/cache/save.
- Use the combined actions/cache when you do not need split control.
- Gate the save on a cache miss to avoid redundant uploads.