A cache that reaches its storage allowance is not evidence that caching failed. It is evidence that the cache is receiving more distinct output than it can retain usefully. The important question is not "how do we make the number go down?" It is "which outputs are being reused, and which are only being uploaded to expire before anyone needs them?" Answer that before changing a limit or deleting a directory.
A storage limit is a cache-quality signal
A build cache has a finite working set: the recent task outputs that another machine is likely to need. That set is usually much smaller than every output every branch has ever produced. When uploads grow faster than useful reuse, the cache churns: new entries displace or age past entries before they can pay back their upload and storage cost.
More storage can be the right answer for a genuinely busy team. But it cannot repair a task that produces a multi-gigabyte output for a small amount of reuse, or a cache key that changes for every run. Treat capacity as a prompt to inspect the shape of the cache first.
Recognize the three common patterns
1. A healthy working set is growing
Cache hits remain high, useful outputs are restored across CI jobs and developer machines, and storage climbs because the repository, team, or build matrix has grown. This is the straightforward case: the cache is doing valuable work and needs enough headroom to keep its working set warm.
2. Large outputs have little reuse
A packaging, integration-test, or container task may produce an archive that is expensive to move and rarely restored. A remote cache is not a warehouse. Keep compact, deterministic task outputs cacheable; reconsider huge artifacts that are only consumed by a later deployment step, or publish those to the artifact system that owns their lifecycle instead.
3. Keys are turning over too quickly
A low hit rate alongside steady uploads usually points to inputs, not storage. Timestamps, generated files, a broad glob, environment-dependent configuration, or a lockfile that changes for unrelated work can cause a new key for nearly every invocation. The cache stays technically correct, but it has no opportunity to reuse a previous result.
For Nx, start by checking the target's declared inputs and outputs; our guide to Nx cache inputs and outputs walks through the usual mistakes. Other build tools have the same underlying question: does the key include every value that changes the output, and only values that change the output?
Investigate before changing the policy
- Start with reuse. Identify the tasks that consume the most storage and compare their size with how often they are restored. The best candidates for tuning are large entries with few hits, not simply the newest entries.
- Separate trusted writers from readers. Let protected-branch CI populate the shared cache and let developer, pull-request, and fork builds restore with read-only tokens where appropriate. This protects the cache and makes it easier to understand which pipeline creates the working set.
- Check for accidental outputs. Dependency directories, test videos, coverage HTML, logs, and deployable bundles often do not belong in a task-output cache. Declare the smallest output directory that lets a restored task be correct.
- Change one variable at a time. Narrowing a cache input may improve reuse, but removing a real input can restore a stale result. Make the change, then verify that a clean build and a restored build produce the same outputs.
Retention is a feature, not a cleanup failure
Expiry prevents an idle cache from becoming permanent storage. In Cachely, managed cache objects currently expire 20 days after upload. An entry that still matters is written again by a later build; an entry that nobody requests disappears without a manual purge. That makes retention a useful boundary around the working set, not a substitute for measuring whether the cache is useful.
Do not respond to every capacity warning by shortening retention. If an artifact is repeatedly restored within the retention window, removing it sooner merely forces recompilation. If it is never restored, the sharper fix is usually to stop caching that task or correct the inputs that make its key churn.
What should happen at the limit
A remote cache is an optimization, not a build dependency. Its failure mode matters as much as its hit rate: a cache that is temporarily unavailable or over its allowance must not turn a correct build into a broken one. The build tool should treat the result as a miss, run the task, and continue.
Cachely follows that contract: an over-quota account returns a cache miss, so the task runs normally instead of failing CI. That preserves correctness while giving you room to inspect usage, tune the cache, or choose more capacity. It also means a sudden run of misses is worth investigating as a performance issue, even when the pipeline is green.
# A useful incident checklist
# 1. Did a storage or request allowance change?
# 2. Which tasks became misses, and were they ever useful hits?
# 3. Did an input, lockfile, environment value, or output directory change?
# 4. Are trusted CI writers still able to populate the cache?
# 5. Does a clean rebuild match the result restored from cache?Choose the smallest durable fix
- Raise capacity when reuse is strong and the working set has genuinely outgrown the current allowance.
- Reduce or split oversized outputs when their transfer and storage cost exceeds their reuse value.
- Correct cache inputs when keys change for values that do not affect the task result.
- Keep retention long enough for real reuse, and let genuinely idle entries expire.
- Keep writes limited to trusted automation. Read-only access for untrusted builds protects shared outputs without giving up the speed of cache reads.
The goal is not a cache that stores everything. It is a cache that keeps the right outputs warm long enough for the next machine to avoid doing the work again. For the broader model - trust boundaries, rollout, and failure behavior - see the remote build cache guide.