The GitHub Actions cache is failing to restore because the key specified in the actions/cache action doesn’t match any previously stored cache, leading to a cache miss and a fresh download/build.

Here are the common reasons this happens and how to fix them:

1. Case Sensitivity Mismatch in Cache Key

GitHub Actions cache keys are case-sensitive. A common mistake is having a slight difference in capitalization between the key used when saving and the key used when restoring.

  • Diagnosis: Examine your actions/cache steps in your workflow file. Pay extremely close attention to the key value in both the save (usually implied by the restore step’s key if it’s not found) and restore steps.
  • Fix: Ensure the key strings are identical, including capitalization. For example, if you used my-app-cache-v1 for saving, use exactly my-app-cache-v1 for restoring.
    - name: Cache dependencies
      uses: actions/cache@v3
      with:
        path: |
          ~/.m2/repository
          ~/.gradle/caches
    
        key: ubuntu-latest-maven-${{ hashFiles('**/pom.xml') }}
    
    
    The fix is to make sure the key value in subsequent runs or different jobs matches exactly.
  • Why it works: The cache lookup is a direct string comparison. Any deviation, including case, results in a new cache being created instead of restoring an existing one.

2. Incorrect hashFiles Pattern

If your cache key includes a hashFiles expression, an incorrect glob pattern or a change in the files matched by the pattern will generate a new hash, thus a new cache key.

  • Diagnosis: Verify the glob pattern in your hashFiles() function. Check if the files you expect to be hashed are actually present in the repository at the time the workflow runs.
  • Fix: Adjust the glob pattern to accurately capture all relevant dependency definition files. For example, if you use Gradle and have build files in subdirectories, ensure your pattern covers them.
    
    key: gradle-cache-${{ hashFiles('**/build.gradle', '**/build.gradle.kts', '**/settings.gradle') }}
    
    
    If you were previously only hashing **/build.gradle and added a build.gradle.kts file, you need to update the hashFiles to include it.
  • Why it works: hashFiles generates a unique hash based on the content of the files matching the pattern. If the set of files or their content changes, the hash changes, leading to a new cache key.

3. Changes in path Argument

While less common for causing a "not found" error (more for incomplete restores), if the path argument in your actions/cache action changes significantly between runs, it might indirectly affect how the cache is identified or perceived.

  • Diagnosis: Compare the path arguments in your actions/cache steps across different workflow runs.
  • Fix: Keep the path argument consistent for a given cache key. If you need to cache different directories, use separate actions/cache steps with distinct keys.
    - name: Cache Node modules
      uses: actions/cache@v3
      with:
        path: node_modules
    
        key: npm-cache-${{ hashFiles('**/package-lock.json') }}
    
    
    If you switch from path: node_modules to path: ./my-app/node_modules, the cache lookup for the old key won’t find the new path, or vice-versa.
  • Why it works: The cache action associates specific paths with a given key. A change in path means the previously saved cache at that key won’t contain the newly specified path, or a new cache will be created for the new path under a potentially different key.

4. Different Operating Systems for Cache Key

If your workflow runs on different operating systems (e.g., ubuntu-latest and windows-latest) and your cache key includes the OS, a mismatch will cause a cache miss.

  • Diagnosis: Check your workflow file for instances where the runs-on directive changes between jobs or workflows, and review if your cache key incorporates this variable.
  • Fix: Either explicitly include the OS in your cache key to ensure OS-specific caches, or remove the OS from the key if you intend for caches to be cross-platform (be cautious, as some cached artifacts can be OS-dependent).
    
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    
    
    If you switch from runner.os to windows-latest in one job and ubuntu-latest in another, they will have different keys.
  • Why it works: Artifacts cached on one OS (like compiled binaries or executables) are often incompatible with another OS. Including runner.os in the key ensures that only compatible caches are restored.

5. Environment Variable Differences

If your cache key relies on environment variables that change between runs (e.g., build versions, feature flags), this will result in a new cache key.

  • Diagnosis: Inspect your cache key definition. Are there any environment variables (e.g., ${{ env.BUILD_VERSION }}) used in the key? Check the values of these variables for the runs where the cache is missed.

  • Fix: Stabilize the environment variables used in the cache key or accept that this variability is expected and will lead to cache churn. If a variable is dynamic and shouldn’t affect the cache, remove it from the key.

    
    key: my-cache-${{ env.APP_VERSION }}
    
    

    If APP_VERSION changes from 1.0.0 to 1.1.0, a new cache key my-cache-1.1.0 will be generated.

  • Why it works: The cache key is a unique identifier. Any change in its constituent parts, including environment variables, creates a new identifier, and thus a new cache.

6. Incorrect Scope of Cache Key (e.g., Job vs. Workflow)

The actions/cache action can define caches at the job level. If you have multiple jobs that should share a cache but use different keys, or if you expect a cache to be available across jobs but it’s only defined within one job’s scope, you’ll see misses.

  • Diagnosis: Review your workflow file for multiple jobs. Check if the actions/cache step is present in all relevant jobs and if the key is consistently defined or if one job is expected to "create" the cache for others.
  • Fix: Ensure that the key used for saving (implicitly by the first restore that misses) and restoring is consistent across all jobs that need to access the same cache. If a cache is truly job-specific, then different keys are appropriate, but a "not found" error implies you expected it to be there.
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Cache build artifacts
            uses: actions/cache@v3
            id: cache
            with:
              path: build/
    
              key: ${{ runner.os }}-build-${{ github.sha }} # Unique key per commit
    
      test:
        runs-on: ubuntu-latest
        needs: build
        steps:
          - uses: actions/checkout@v3
          - name: Restore build artifacts
            uses: actions/cache@v3
            with:
              path: build/
    
              key: ${{ runner.os }}-build-${{ github.sha }} # Must match build job's key
    
    
  • Why it works: Caches are looked up by their exact key. If job A saves a cache with key X and job B tries to restore cache with key Y, job B will miss, even if the content of the cache is the same, because the identifier is different.

After fixing these, you might encounter a "Cache not found, creating new cache" message, which is the expected behavior when a cache truly doesn’t exist yet. The next potential issue is often related to the size of the cache exceeding GitHub’s limits, or specific file permissions preventing the cache from being correctly uploaded.

Want structured learning?

Take the full Github-actions course →