🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

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.

DevOpsBoysMay 14, 20264 min read
Share:Tweet

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:

yaml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ github.sha }}   # Commit SHA changes every push!

Correct — key based on lock file hash:

yaml
- 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:

yaml
- uses: actions/cache@v4
  with:
    path: ~/.npm        # Global npm cache, not node_modules
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}

Python pip:

yaml
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }}

Maven:

yaml
- uses: actions/cache@v4
  with:
    path: ~/.m2/repository
    key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml') }}

Go:

yaml
- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/go-build
      ~/go/pkg/mod
    key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}

Docker layers (buildx):

yaml
- 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:

yaml
- 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.

yaml
# 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:

yaml
# 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:

bash
gh cache list --repo owner/repo
gh cache delete <cache-id> --repo owner/repo

Issue 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.

yaml
# 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:

yaml
- 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:

yaml
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 fine

Verify Your Cache is Working

yaml
- 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

ProblemFix
Cache miss every runUse hashFiles() not github.sha for key
Hit but no speedupWrong path — check tool's actual cache dir
Feature branch always missAdd restore-keys to fall back to main
Cache not savedAdd if: always() to save step
10GB limit hitDelete old caches, split keys
Fork PRs missExpected — forks can't write cache

For hands-on CI/CD labs including GitHub Actions optimization, KodeKloud has courses with real pipeline debugging exercises.

Newsletter

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

Comments