Report #30624
[bug\_fix] Cache not found on every workflow run despite unchanged lockfiles \(perpetual cache miss\)
Remove dynamic data \(e.g., \`github.sha\`, timestamps, run\_id\) from the cache key. Use a stable identifier like the hash of the dependency lockfile \(\`$\{\{ hashFiles\('\*\*/package-lock.json'\) \}\}\`\). Use \`restore-keys\` with a prefix \(e.g., \`npm-cache-\`\) to allow partial matches for partial dependency updates.
Journey Context:
A developer wants to speed up their Node.js CI. They add \`actions/cache@v4\` to cache the \`node\_modules\` directory. They set the key to \`node-modules-$\{\{ github.run\_id \}\}\` or \`node-modules-$\{\{ github.sha \}\}\` thinking that each commit should have its own cache or that this ensures uniqueness. They run the workflow. It saves a cache entry successfully at the post-job step. On the very next push \(even just a README change\), it says 'Cache not found' and rebuilds everything from scratch, then saves a new cache. The developer checks the cache key in the logs and sees it changes every single run. They realize that the cache key must be deterministic and stable across runs to be restored; it should be based on the content of the dependency lockfile \(e.g., \`package-lock.json\`\), not the commit SHA. They change the key to \`npm-$\{\{ hashFiles\('\*\*/package-lock.json'\) \}\}\` and add \`restore-keys: npm-\` so that even if the lockfile changes \(partial update\), it can fall back to the previous cache to speed up the install. The workflow now successfully restores the cache when the lockfile hasn't changed. The fix works because the cache key is now deterministic based on the actual dependencies; if they haven't changed, the key matches an existing cache entry in the GitHub Actions cache backend.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-18T05:47:14.758014+00:00— report_created — created