Fix GitHub Actions Cache Not Found: Root Causes and Minimal Checks

Topic: github-actions-cache-not-found-fixUpdated 8/3/2026

Quick Answer

  • Conclusion: GitHub Actions cache misses are almost always caused by unstable cache keys, incorrect cache paths, or branch scope restrictions—not by GitHub infrastructure issues.
  • First checks: Verify your cache key uses hashFiles() on a committed lock file (e.g., package-lock.json, requirements.txt), confirm the cache path matches the tool's actual cache directory, and ensure the cache was saved on the default branch.
  • Minimal fix: Use the official setup-* actions (e.g., setup-node, setup-python) with their built-in cache input instead of manually configuring actions/cache.
  • Environment boundary: Applies to all GitHub-hosted runners (Ubuntu, Windows, macOS); cache scope is per-branch (except the default branch), with a 7-day inactivity eviction and a 10GB total repository limit.

What Problem It Solves

GitHub Actions caching speeds up CI/CD workflows by reusing dependency installation artifacts across runs. The actions/cache action saves and restores directories like package manager caches, but misconfiguration leads to the frustrating Cache not found for input keys error. This article covers the root causes and provides concrete fixes for cache misses, unstable keys, and ineffective cache paths.

When This Error or Setup Appears

The cache-not-found error appears in these typical scenarios:

ScenarioSymptom
PR buildsCache saved on a feature branch is unavailable to other branches
Lock file regeneratedhashFiles() produces a different hash every run
Wrong cache pathCache restores successfully but dependencies still reinstall
Low-traffic repoCache evicted after 7 days of inactivity
Cache size limitRepository exceeds 10GB total cache storage

Minimal Working Configuration

The simplest reliable approach is to use the official setup-* actions, which handle cache keys and paths automatically:

YAML
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js with cache
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

For Python projects:

YAML
      - name: Set up Python with cache
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'

      - name: Install dependencies
        run: pip install -r requirements.txt

For Java/Gradle:

YAML
      - name: Set up Java with cache
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
          cache: 'gradle'

If you must use actions/cache manually, this is the minimal correct pattern:

YAML
      - name: Cache npm
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

Root Cause Analysis

1. Unstable Cache Keys

The most common cause of cache misses. The cache key must be deterministic—it should change only when dependencies change.

Bad key (changes every run):

YAML
key: ${{ runner.os }}-node-${{ github.sha }}

Good key (changes only when lock file changes):

YAML
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

Gotcha: If a workflow step modifies the lock file before the cache step (e.g., running npm install instead of npm ci), the hash changes on every run. Always commit lock files and use npm ci or pip install -r requirements.txt without regenerating them.

2. Incorrect Cache Path

Caching the wrong directory makes the cache useless even when it hits.

Package ManagerCorrect Cache PathWrong Path (Common Mistake)
npm~/.npmnode_modules
pip~/.cache/pipsite-packages
Gradle~/.gradle/cachesbuild/
Go$(go env GOCACHE)vendor/

Caching node_modules fails because npm ci deletes it before installing, and it's not portable across OS/Node versions.

3. Branch Scope Restrictions

GitHub caches are scoped to the branch where they were saved. PR builds can only read caches from the base branch (e.g., main), not from the feature branch. If you only push to feature branches without merging, caches never populate for PRs.

Fix: Ensure the default branch runs the workflow on push so caches are saved there:

YAML
on:
  push:
    branches: [main]
  pull_request:

4. Cache Eviction and Limits

  • Caches not accessed for 7 days are evicted.
  • Total repository cache storage is limited to 10GB (per-repo, not per-branch).
  • When the limit is reached, the oldest caches are removed.

For low-traffic repositories, consider a scheduled workflow to keep caches warm:

YAML
on:
  schedule:
    - cron: '0 0 */5 * *'  # every 5 days

Common Errors and Fixes

Error: Cache not found for input keys: Linux-node-abc123def456...

Cause: The key doesn't match any saved cache. Either the key is unstable, or the cache was never saved on the accessible branch.

Fix:

  1. Check if hashFiles() references a file that gets regenerated during the workflow.
  2. Confirm the cache was saved on the default branch.
  3. Add restore-keys for partial matching:
YAML
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
  ${{ runner.os }}-node-

Error: Cache restored successfully but node_modules is still empty

Cause: The cache path doesn't match where the tool actually stores data.

Fix: Use the tool's global cache directory, not the project's install directory:

YAML
# Wrong
path: node_modules

# Correct
path: ~/.npm

Error: Cache key changes on every run despite no dependency changes

Cause: A step before the cache step modifies the lock file.

Fix:

  1. Verify the lock file is committed to the repository.
  2. Use npm ci instead of npm install (or pip install -r requirements.txt without --upgrade).
  3. Add a debug step to print the hash:
YAML
      - name: Debug hash
        run: echo "Hash: ${{ hashFiles('**/package-lock.json') }}"

Production Notes and Security Checks

Cache Key Requirements

  • Base the key on immutable files (lock files), never on timestamps or commit SHAs.
  • Use restore-keys to fall back to the most recent compatible cache.
  • Include the runner OS in the key to avoid cross-platform mismatches.

Security Considerations

  • Caches are accessible to all branches of the same repository—never store tokens, secrets, or credentials in cached content.
  • If your workflow uses pip install -r requirements.txt, ensure the requirements file doesn't contain private package URLs with embedded credentials.
  • Consider using actions/cache with enableCrossOsArchive: true only when you explicitly need cross-OS cache sharing (rare).

Performance Tips

  • Cache the package manager's global cache, not the installed dependencies.
  • For monorepos, split caches by subproject to avoid invalidating the entire cache when one dependency changes.
  • Monitor the repository's Caches page (Settings → Actions → Caches) to see hit rates and eviction activity.

FAQ

Q: Why does my GitHub Actions cache always miss?

A: The most common causes are: (1) an unstable cache key—hashFiles() references a file regenerated during the workflow; (2) an incorrect cache path—you cache node_modules instead of ~/.npm; (3) branch scope—PR builds can only read caches from the base branch; (4) eviction—the cache was inactive for 7 days or the 10GB repo limit was exceeded. Check the cache key stability first, then verify the path and branch scope.

Q: Should I cache node_modules or ~/.npm?

A: Cache ~/.npm (the npm global cache), not node_modules. node_modules is not portable across operating systems or Node versions, and npm ci deletes it before reinstalling, making the cache useless. Caching ~/.npm lets npm ci install dependencies from the local cache, dramatically speeding up builds.

Q: How do I debug whether the GitHub Actions cache hit or missed?

A: Enable ACTIONS_STEP_DEBUG by setting it to true in the workflow or repository secrets to see detailed cache lookup logs. In the GitHub UI, go to the repository's Actions → Caches page to view saved caches and their last-used timestamps. You can also add a debug step to print the computed cache key: run: echo "Cache key: ${{ hashFiles('**/package-lock.json') }}".

Related Guides