GitHub Actions Cache Not Working — How to Fix It
Your workflow runs are still slow even with actions/cache. Cache miss every time, cache key conflicts, wrong paths — here's how to diagnose and fix GitHub Actions caching.
You added actions/cache to your workflow. Builds are still as slow as before. Cache not found for input keys shows up every run. Here's how to fix it.
How GitHub Actions Cache Works
Job starts
→ actions/cache runs (restore phase)
→ Checks for cache hit by key
→ If hit: restores files from cache
→ Job runs (your steps)
→ actions/cache runs (save phase, post-job)
→ If cache miss: saves files to cache for next run
Important: The first run after you add caching will always be a cache miss. The cache is saved at the end of that run, then available on subsequent runs.
Issue 1 — Cache Key Never Matches
The most common problem. Your cache key changes every run, so you always get a miss.
Wrong — dynamic key that always changes:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ github.sha }} # Commit SHA changes every push!Correct — key based on lock file hash:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-The hashFiles() function creates a hash of your lock file. Cache only invalidates when dependencies actually change.
Issue 2 — Wrong Cache Path
If you cache the wrong directory, you'll get cache hits but no performance improvement.
Node.js:
- uses: actions/cache@v4
with:
path: ~/.npm # Global npm cache, not node_modules
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}Python pip:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }}Maven:
- uses: actions/cache@v4
with:
path: ~/.m2/repository
key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml') }}Go:
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}Docker layers (buildx):
- uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: buildx-${{ runner.os }}-${{ github.sha }}
restore-keys: |
buildx-${{ runner.os }}-Issue 3 — Cache on Different Branches Not Sharing
By default, GitHub caches are isolated by branch. The main branch cache is not available to feature branches on the first run.
Solution — use restore-keys to fall back to main:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
npm-${{ runner.os }}-restore-keys are tried in order if the main key misses. A partial cache hit is better than none.
Issue 4 — Cache Size Limit Exceeded
GitHub limits cache size to 10 GB per repository. When exceeded, oldest caches are evicted.
# Check cache usage in your repo:
# Settings → Actions → Caches
# Or via API:
gh api /repos/{owner}/{repo}/actions/caches | jq '.actions_caches[] | {key: .key, size_in_bytes: .size_in_bytes}'Fix — scope caches more specifically:
# Instead of one big cache key, split by environment:
key: npm-${{ runner.os }}-production-${{ hashFiles('package-lock.json') }}
key: npm-${{ runner.os }}-development-${{ hashFiles('package-lock.json') }}Or delete stale caches:
gh cache list --repo owner/repo
gh cache delete <cache-id> --repo owner/repoIssue 5 — Cache Not Saving (Job Cancelled or Failed Early)
The cache post-step only runs if the main job steps complete. If your job fails or is cancelled before the post-step, nothing is saved.
# Force cache save even on failure
- uses: actions/cache/save@v4
if: always() # Run even if previous steps failed
with:
path: ~/.npm
key: ${{ steps.cache.outputs.cache-primary-key }}Using actions/cache/restore and actions/cache/save separately gives you more control:
- name: Restore cache
id: cache-restore
uses: actions/cache/restore@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
# ... your build steps ...
- name: Save cache
uses: actions/cache/save@v4
if: always()
with:
path: ~/.npm
key: ${{ steps.cache-restore.outputs.cache-primary-key }}Issue 6 — Forks Can't Write to Cache
Pull requests from forks can read the base repo's cache but cannot write to it (security restriction).
This means the first run of every forked PR will be a cache miss, and the cache won't be saved. This is expected behavior, not a bug.
Workaround — workflow for fork PRs:
on:
pull_request:
push:
branches: [main]
jobs:
build:
steps:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
# Cache will be read-only for fork PRs — that's fineVerify Your Cache is Working
- name: Restore npm cache
id: npm-cache
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
# Check if cache was hit
- name: Log cache status
run: |
echo "Cache hit: ${{ steps.npm-cache.outputs.cache-hit }}"In the workflow logs, look for:
Cache restored successfully from key: ...— full hit ✅Cache restored from key: ...— partial hit via restore-keys ⚠️Cache not found for input keys: ...— miss ❌
Quick Fix Checklist
| Problem | Fix |
|---|---|
| Cache miss every run | Use hashFiles() not github.sha for key |
| Hit but no speedup | Wrong path — check tool's actual cache dir |
| Feature branch always miss | Add restore-keys to fall back to main |
| Cache not saved | Add if: always() to save step |
| 10GB limit hit | Delete old caches, split keys |
| Fork PRs miss | Expected — forks can't write cache |
For hands-on CI/CD labs including GitHub Actions optimization, KodeKloud has courses with real pipeline debugging exercises.
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
GitHub Actions Docker Push: Permission Denied / Unauthorized Fix (2026)
Getting 'permission denied' or 'unauthorized: authentication required' when pushing Docker images in GitHub Actions? Here are all the causes and fixes.
GitHub Actions Job Timeout — Every Fix (2026)
Your GitHub Actions job times out after 6 hours or hits a custom timeout limit. Here's every cause — hung Docker builds, hanging tests, stuck deployments, missing timeout config — and the exact fix.
GitHub Actions 'No Space Left on Device': How to Fix Runner Disk Issues
GitHub Actions failing with 'no space left on device'? Here's how to free disk space on runners, optimize Docker builds, and handle large monorepos.