Most likely the cache was evicted. GitHub Actions removes cache entries that have not been accessed for over 7 days, and it deletes the oldest by last access when a repository passes its size limit, 10 GB by default. A quiet week or a busy branch can both empty a cache you thought was safe.
The two rules that cause misses
GitHub's documentation on usage limits and eviction says:
- Any cache entry not accessed in over 7 days is removed.
- There is no limit on the number of caches, but the total size per repository is limited. The default is 10 GB, and organization or repository admins can raise it. Usage beyond the limit is billed.
- When the limit is reached, eviction deletes caches in order of last access date, from oldest to most recent. A new cache is still saved, and older ones go to make room.
Other CI systems have their own rules, so look up yours. The reasoning below applies to any of them.
Three situations that look like a bug
- The holiday or freeze. Nothing ran for 8 days, so every entry aged out. Monday's first builds are all slow and all hit the registry at once.
- Branch churn. Many feature branches each save a large cache. Together they pass the size limit, and the long lived cache that main depends on has the oldest access time, so it is evicted first.
- A key that changes too often. If the key includes something that changes every run, such as a timestamp or a commit hash, every run saves a new entry and nothing is reused. The cache fills with one use entries.
How to diagnose it
- List the caches for the repository and sort by last access. Look for the entry you expected.
- Check the total size against the limit. If you are near it, you have a churn problem, not a 7 day problem.
- Compare the cache key in a slow run with the key in a fast run. A key mismatch means the cache was never going to hit.
- Look at the cache size per entry. Caching the installed dependency tree can be much larger than caching the package download cache. See does setup-node cache node_modules.
Fixes
- Key the cache on the lockfile hash and nothing else that varies. The key should change when dependencies change and not otherwise.
- Cache the download cache, not the full installed tree, to keep entries small.
- Keep the long lived cache fresh with a scheduled job that touches it inside every 7 day window. The steps are in warm CI dependency caches before a release day burst.
- Do not save a cache from every feature branch. Let branches restore and let main save.
- Point installs at a mirror so a cold cache is slow but never blocked by a public registry limit.
A security angle on the same cache
Caches are shared state, and shared state can be poisoned. A cache that quietly vanishes and a cache that quietly changes are different problems, but both come from not treating the cache as a managed asset. Read can fork pull requests poison your GitHub Actions cache and purge CI caches after a compromise so you know how to clear one deliberately.
Where Safeguard fits
When caches rebuild after an eviction, the dependency set can drift if your install is not locked. Safeguard's SBOM and dependency scanning compares what each build contains, and policies and gates can stop a drifted build from merging.
Quick check
Does raising the size limit fix misses?
It helps if you are evicting for size. It does nothing for the 7 day rule, so a quiet week still empties the cache.
Is a cache miss a security problem?
Not by itself. It is a speed and reliability problem. It becomes a security concern if the rebuild resolves different versions than before, which a lockfile prevents.
Sources
Last reviewed: 19 October 2026.