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/cachesteps in your workflow file. Pay extremely close attention to thekeyvalue in both thesave(usually implied by therestorestep’skeyif it’s not found) andrestoresteps. - Fix: Ensure the
keystrings are identical, including capitalization. For example, if you usedmy-app-cache-v1for saving, use exactlymy-app-cache-v1for restoring.
The fix is to make sure the- name: Cache dependencies uses: actions/cache@v3 with: path: | ~/.m2/repository ~/.gradle/caches key: ubuntu-latest-maven-${{ hashFiles('**/pom.xml') }}keyvalue 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.
If you were previously only hashingkey: gradle-cache-${{ hashFiles('**/build.gradle', '**/build.gradle.kts', '**/settings.gradle') }}**/build.gradleand added abuild.gradle.ktsfile, you need to update thehashFilesto include it. - Why it works:
hashFilesgenerates 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
patharguments in youractions/cachesteps across different workflow runs. - Fix: Keep the
pathargument consistent for a given cache key. If you need to cache different directories, use separateactions/cachesteps with distinct keys.
If you switch from- name: Cache Node modules uses: actions/cache@v3 with: path: node_modules key: npm-cache-${{ hashFiles('**/package-lock.json') }}path: node_modulestopath: ./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-ondirective 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).
If you switch fromkey: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}runner.ostowindows-latestin one job andubuntu-latestin 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.osin 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_VERSIONchanges from1.0.0to1.1.0, a new cache keymy-cache-1.1.0will 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/cachestep is present in all relevant jobs and if thekeyis consistently defined or if one job is expected to "create" the cache for others. - Fix: Ensure that the
keyused for saving (implicitly by the firstrestorethat 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
Xand job B tries to restore cache with keyY, 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.