The GitHub Actions Cache Size Limit, Explained
GitHub Actions caps all caches in a repository at 10 GB by default and evicts least recently used entries first. How the ceiling works, how to remove it.
GitHub Actions limits the combined size of all caches in a repository to 10 GB by default, and once the total passes that ceiling GitHub deletes entries in least recently used order until the repository fits again. That eviction is why a cache key that hit yesterday can miss this morning with no change to your workflow. WarpBuild removes the ceiling: the WarpBuild cache is a drop-in replacement for actions/cache@v4 with no repository size limit, and switching to it is a one-line change.
Diagnosis
Three rules decide how long a GitHub Actions cache entry survives. All three come from GitHub's caching documentation, checked on 2026-08-13:
- A repository can hold any number of cache entries, but GitHub caps their combined size at 10 GB by default.
- When the total exceeds the cap, GitHub evicts caches in order of least recent access until the repository is back under the limit.
- Any entry that goes unused for 7 days is deleted, whatever the total size.
GitHub has since layered administrative controls on top of the default: organization admins can adjust the cache size limits and the expiration policy under the organization's Actions settings. Most repositories still run with the 10 GB default, and everything below assumes it.
Start by measuring instead of guessing. The Caches view under the repository's Actions tab lists every entry with its size, its branch scope, and when it was last used. The GitHub CLI returns the same data sorted by weight:
gh cache list --sort size_in_bytes --limit 30The REST endpoint GET /repos/{owner}/{repo}/actions/cache/usage returns the active entry count and the total size in bytes, which is the exact number the 10 GB ceiling applies to. If that total sits near the cap, eviction is already happening; the last-accessed timestamps tell you whether an entry disappeared because it aged past 7 days or because something newer pushed it out.
Four failure modes account for most reports that read "the cache was there yesterday".
Cache evicted between runs
The ceiling applies to the whole repository, so unrelated workflows and branches compete for the same 10 GB. A 3 GB dependency cache per active branch across four branches is 12 GB, already over the cap before any other workflow stores a byte. GitHub then evicts whichever entry was accessed longest ago, and that is often the default branch entry that every pull request branch falls back to. The symptom is a restore step logging "Cache not found for input keys" on a key that hit the previous day, with no lockfile change in between. A busy afternoon of pull requests can evict the morning's caches; a nightly scheduled workflow that writes large entries can evict everything else while the team sleeps.
Key churn on lockfile hashes
A key such as ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} writes a brand new full-size entry every time the lockfile changes. The superseded entries linger for up to 7 days before the age rule collects them. A week of daily dependency bumps leaves seven near-identical copies of the same dependency tree in the store, and at 3 GB per copy that is 21 GB of dead weight pressed against a 10 GB ceiling. The entries you still need get evicted to make room for entries nobody will restore again.
restore-keys fallback returning a stale entry
restore-keys performs a prefix match and restores the most recent entry whose key matches. That keeps builds warm across lockfile changes, but it has two sharp edges. First, the cache-hit output is only true on an exact primary key match, so a step guarded with if: steps.cache.outputs.cache-hit != 'true' still runs after a fallback restore; teams that instead skip installs on any restore ship stale dependencies. Second, saving on top of a stale restore compounds. The restored directory keeps packages the lockfile dropped, the save step writes them back under the new key, and entries grow a little with every generation. Growing entries fill the ceiling faster, which accelerates the eviction described above.
Per-branch cache scoping
A workflow run can restore caches created on its own branch or on the base branch, including the default branch. Sibling feature branches cannot read each other's entries. Each branch therefore writes its own copy of a mostly identical dependency cache, and five active branches at 2.5 GB each hold 12.5 GB. The scoping rule is a sensible security boundary, and it also means the effective budget per branch is far smaller than 10 GB on any repository with real parallel activity.
Fix
Replace actions/cache@v4 with WarpBuilds/cache@v1. The action is a drop-in replacement: the same path, key, and restore-keys inputs, the same cache-hit output, and matching restore and save subactions. The storage behind it has no repository ceiling, so entries stop competing for space and size-based eviction stops happening.
- uses: actions/cache@v4
+ uses: WarpBuilds/cache@v1Here is a complete workflow with the key and restore-keys shape most Node repositories use:
name: build
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- name: Restore npm cache
id: npm-cache
uses: WarpBuilds/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- run: npm ci
- run: npm testOne placement condition applies. The cache is available on Linux-based runners, both x64 and ARM64, where it is enabled by default, and WarpBuild caches are not supported for Windows-based runners. The workflow above runs on warp-ubuntu-latest-x64-4x, a 4 vCPU, 16 GB Ubuntu runner; the runs-on label is the only thing that moves a job onto WarpBuild.
Expect the first run after the swap to be cold, because the WarpBuild store starts empty. From the second run on, the same keys resolve against storage that never evicts for space. Retention becomes purely time-based: entries expire 7 days after their last use, the same sliding window GitHub applies, so an entry restored every weekday stays warm indefinitely. Entries can be removed early with the delete-cache input or from the WarpBuild console.
For toolchain and package manager caching there is a shorter path than writing keys by hand. WarpBuild maintains drop-in setup actions for Node.js, Python, Go, Java, .NET, Ruby, Zig, Rust, Gradle, and mise. WarpBuilds/setup-node@v6 accepts the same inputs as actions/setup-node, and its cache: npm input routes dependency caching to WarpBuild storage automatically.
Know the boundary of what a cache action can hold. It stores files and directories you name by path. Machine state that lives outside a cacheable path, such as a warmed Docker daemon or a fully hydrated monorepo working directory, belongs to snapshot runners, which boot later jobs from a VM image captured mid-workflow. The persistent caches guide covers which mechanism fits which kind of state.
Configuration
Key design carries over unchanged, because WarpBuilds/cache scopes entries to key, version, and branch exactly as GitHub does. The pattern that behaves best under lockfile churn is an exact key on the hash plus a prefix fallback:
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-An exact hit restores the current dependency set. A fallback hit restores the newest earlier entry, and npm ci reconciles the difference. Keep install steps unconditional unless you gate them on an exact hit, since cache-hit stays false on fallback restores.
When the save should happen regardless of whether later steps fail, split the action into its restore and save halves:
- name: Restore dependencies
id: deps-restore
uses: WarpBuilds/cache/restore@v1
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
- name: Save dependencies
if: always()
uses: WarpBuilds/cache/save@v1
with:
path: ~/.npm
key: ${{ steps.deps-restore.outputs.cache-primary-key }}Branch scoping still applies, so seed shared entries from the default branch. A push-triggered workflow on main writes the entry once, and every pull request branch reaches it through restore-keys. That single change removes most duplicate per-branch copies, which mattered enormously under a 10 GB ceiling and still keeps storage costs tidy without one.
The action also carries the operational inputs from upstream: fail-on-cache-miss: true turns a missing entry into a hard failure for jobs that must never build cold, lookup-only: true checks for existence without downloading, and enableCrossOsArchive allows entries saved on one operating system to restore on another.
Three details from the caching documentation save debugging time:
- Inside a container, the action needs
wgetandzstdinstalled, and theWARPBUILD_RUNNER_VERIFICATION_TOKENenvironment variable passed through to the container to authenticate against the cache service. - Cache versions hash the compression tool and the cached paths. An entry saved on a macOS runner will never restore on a Linux runner; they are distinct caches by construction.
- Docker layer caching runs through the same backend. Point buildx at
type=gha,url=http://127.0.0.1:49160/,version=1incache-fromandcache-to, withmode=maxto cache intermediate layers. For image-heavy pipelines, remote Docker builders keep a persistent layer cache on a dedicated build VM instead, which avoids moving layers in and out of cache storage on every run; the Docker layer caching guide compares the two setups.
Spending stays visible after the switch: cache usage appears as its own line in the Cache Billing report, split into storage and operation costs, so growth shows up in a chart rather than as mystery misses.
Cost or Time Model
Prices first. Swapping the cache action does not require swapping runners, so the worked model below prices runner minutes at WarpBuild's own rate for the workflow's runner and isolates miss count as the only variable. WarpBuild's Linux runner rates, from the pricing page:
| Runner label | vCPU | RAM | Price per minute |
|---|---|---|---|
| warp-ubuntu-latest-x64-2x | 2 | 8 GB | $0.004 |
| warp-ubuntu-latest-x64-4x | 4 | 16 GB | $0.008 |
| warp-ubuntu-latest-x64-8x | 8 | 32 GB | $0.016 |
| warp-ubuntu-latest-x64-16x | 16 | 64 GB | $0.032 |
| warp-ubuntu-latest-x64-32x | 32 | 128 GB | $0.064 |
| warp-ubuntu-latest-arm64-2x | 2 | 8 GB | $0.003 |
| warp-ubuntu-latest-arm64-4x | 4 | 16 GB | $0.006 |
| warp-ubuntu-latest-arm64-8x | 8 | 32 GB | $0.012 |
| warp-ubuntu-latest-arm64-16x | 16 | 64 GB | $0.024 |
| warp-ubuntu-latest-arm64-32x | 32 | 128 GB | $0.048 |
Cache add-on rates:
| Metric | Hosted runners | BYOC |
|---|---|---|
| Cache storage | $0.20 per GB-month | Free |
| Cache write or restore | $0.0001 per operation | Free |
Now the model. Assumptions, stated so you can substitute your own numbers:
- 40 workflow runs per weekday, 22 weekdays per month.
- A cold dependency install takes 6 minutes; restoring a warm cache takes 1 minute. Each miss therefore costs 5 extra minutes.
- The dependency cache entry is 3 GB, four branches stay active, and lockfile churn keeps roughly 18 GB of entries live at any moment.
- Under the 10 GB ceiling, eviction, key churn, and branch scoping together produce 12 misses per day, a 30 percent miss rate, which is a realistic shape once a repository's cache working set is about double its ceiling.
- Without a ceiling, misses happen only when a lockfile actually changes: 2 per day.
Time cost. Twelve misses at 5 extra minutes is 60 minutes of added pipeline time every day, spread across the day's runs. Over a month that is 22 hours of runner time spent rebuilding artifacts that were already built and then evicted.
Money cost. Both columns below price runner minutes at $0.008, the warp-ubuntu-latest-x64-4x list rate from the table above, because the runner does not change between the two scenarios; only the cache backend does. The difference comes from miss counts alone.
| Line | 10 GB ceiling, GitHub cache | No ceiling, WarpBuild cache |
|---|---|---|
| Cache misses per day | 12 of 40 runs | 2 of 40 runs |
| Extra runner minutes per month | 1,320 | 220 |
| Runner cost of misses per month | $10.56 | $1.76 |
| Cache storage per month | $0 within the cap | $3.60 for 18 GB at $0.20 per GB-month |
| Cache operations per month | $0 | $0.18 for 1,760 at $0.0001 each |
The storage and operation charges land on the WarpBuild side and total $3.78 per month for this workload; on BYOC both are free. The trade is explicit: pay for the gigabytes your caches actually occupy, and stop paying rebuild minutes for entries that eviction destroyed. On larger runners, on bigger caches, or on teams with more branches, the rebuild column grows much faster than the storage column, which is worth checking against your own gh cache list output before you decide.
Two structural notes belong in any cost comparison. Cache behavior is one lever among several; the guide to speeding up GitHub Actions covers runner sizing and the rest. On the outcome side, watch the cache hit rate in the Cache Billing report after the switch: it is usually the first number that moves once the ceiling stops applying.
FAQ
What is the GitHub Actions cache size limit?
GitHub caps the combined size of all caches in a repository at 10 GB by default. Once the total passes the cap, GitHub evicts entries in least recently used order until the repository is back under the limit, and any entry unused for 7 days is deleted regardless of the total. Checked on 2026-08-13 against GitHub's caching documentation.
Does the WarpBuild cache have a size limit?
No. The WarpBuild cache has no repository size ceiling, so entries are never evicted to make room for newer ones. Entries expire 7 days after their last use. Storage is billed at $0.20 per GB-month and operations at $0.0001 each on hosted runners; both are free on BYOC.
Is WarpBuilds/cache compatible with actions/cache@v4?
Yes. WarpBuilds/cache@v1 is a drop-in replacement for actions/cache@v4. It accepts the same path, key, and restore-keys inputs, produces the same cache-hit output, and ships matching restore and save subactions, so a workflow switches by changing one line.
Does the WarpBuild cache work on Windows runners?
No. WarpBuild caches are not supported for Windows-based runners. The cache is available on Linux-based runners, both x64 and ARM64, where it is enabled by default.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.