Cache service responded with 503 or 429 in GitHub Actions
Cache service responded with 503 in GitHub Actions is the Actions cache backend refusing one request, and a 429 is the same backend throttling you. Neither is a problem with your key: the step warns, the job carries on without a cache, and the only thing that turns it into a red build is a workflow that treats a cache miss as fatal.

What this error means
A cache step logs a warning rather than an error, and the job keeps going: "Warning: Failed to restore: Cache service responded with 503". A 429 appears in the same shape and means the opposite thing, that the service is working and has decided you are asking too often. Either way the restore did not happen, so the steps after it run cold: the install downloads everything, the build has no incremental state, and the job takes the long route. The save at the end of the job can fail the same way, which is worse in one specific sense, because the next run then starts cold too. The block below is a sample from the pattern library rather than from a run of ours, and the section on reproduction explains why there is no run on this page.
Warning: getCacheEntry failed: Cache service responded with 503A failed restore is a warning, by design
The cache action is an optimisation and it behaves like one. GitHub documents the same posture for the access-mode case: when a cache operation is skipped, "the action logs an informational message and the step and run continue without failing", and a read-only run that tries to save gets a failed save while "the step and the job do not" fail.
So a 503 on its own cannot turn your build red. If your build did go red, something downstream insisted: fail-on-cache-miss, a step guarded on cache-hit, or an install step that assumed the dependencies were already there. That is the thing to find, and it is usually three lines below the warning.
grep -n "cache-hit\|fail-on-cache-miss" .github/workflows/*.ymlCommon causes
The cache service had a bad few minutes
The ordinary case. A 503 is the backend declining one request during an incident, a deploy or a load spike, and it clears on its own. Nothing in the workflow caused it and nothing in the workflow will prevent it; the only useful response is to make sure it costs a slower build rather than a failed one.
You set continue-on-error and the job still failed
The wasted fix, because the tolerance went on the step that was already tolerant. The cache step warns; the failure is in whatever ran next on the assumption that the cache was restored. Look for a step gated on the cache-hit output, a fail-on-cache-miss, or a test command that expects a directory the restore was supposed to create.
Your own concurrency earned the 429
A wide matrix, several workflows triggered by one push, or a monorepo running twelve jobs that each save a large cache will produce a burst of cache traffic in a short window. The service answers that with a throttle. It is transient, and it is also a design signal: the same caches are being written many times over.
The caches are large enough to make every operation fragile
A multi-gigabyte cache takes a long time to upload and download, which widens the window in which something can go wrong, and it pushes other entries out under the eviction policy. In our experience the repositories that see cache-service errors most often are the ones caching a built tree rather than a package store.
How to fix it
Let a cache miss be a cache miss
- Remove
fail-on-cache-missunless the job genuinely cannot run without the cache. - Make sure the install step runs whether or not the restore succeeded, rather than being skipped on a cache hit.
- Re-run the workflow once the service recovers, and expect the first run after the incident to be slow.
- uses: actions/cache@v6
id: cache
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
- run: npm ci --no-audit --no-fundPut the tolerance where the failure is
If something downstream really does need the cache, guard that step rather than making the whole job depend on a service you do not control. A conditional fallback is a few lines and it turns an outage into a slow build.
- uses: actions/cache@v6
id: deps
with:
path: ~/.cache/pip
key: pip-${{ hashFiles('requirements.txt') }}
- if: steps.deps.outputs.cache-hit != 'true'
run: pip install -r requirements.txtReduce the cache traffic you generate
Cap matrix parallelism, share one warm cache across legs with restore-keys, and cache the package store instead of the installed tree. Three smaller entries that restore reliably beat one large entry that is evicted on Tuesday.
strategy:
max-parallel: 4
matrix:
node: [20, 22, 24]Check whether it is you or them
Before changing the workflow, look at the status page and at how many cache operations the run made. A single 503 in one job during an incident needs no change at all. The same warning in every job of a wide matrix, repeatedly, is your own load and the fix is above.
A 429 is usually your own matrix
A 503 is the service having a bad minute and is nobody's fault. A 429 is a rate limit, and rate limits are a response to a request rate, so the first place to look is the shape of your own workflow. A twenty-leg matrix that each saves its own multi-gigabyte cache at roughly the same second is a burst by any measure.
Two changes cut it without giving up caching. Cap how many legs run at once, and let the legs share one warmed cache through restore-keys instead of each writing its own.
strategy:
max-parallel: 4
matrix:
python: ['3.11', '3.12', '3.13']
steps:
- uses: actions/cache@v6
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-Size and eviction, so you know what you are asking for
GitHub documents the default limit as 10 GB per repository, and says it "can be increased by enterprise owners, organization owners, or repository administrators". It also removes "any cache entries that have not been accessed in over 7 days", and when the limit is reached it deletes "in order of last access date, from oldest to most recent".
Those two rules explain most "the cache was there yesterday" reports, and they also explain why a repository that caches everything ends up caching nothing useful: a handful of large entries push out the small ones that were doing the work.
What the runner detects, and why there is no run on this page
Latchkey carries GHA_CACHE_RESTORE_FAIL at confidence 0.86, matching the getCacheEntry wording and the cache-service status line, and its plan is a short retry: two attempts starting at 5 seconds. The library files it as a documented transient and notes the same asymmetry this page opens with, that "the cache step itself is non-fatal but a downstream step expecting cached deps will fail".
Every other failure page in this cluster carries a recorded run. This one does not, and the reason is worth stating plainly: the Actions cache service answers requests from inside a GitHub Actions job and nowhere else, so there is no way to reproduce a 503 from it on a Latchkey runner without fabricating one. A stub returning 503 would produce a screenshot, not evidence. The sample above is the pattern library's, and this page makes no claim about a repair we have not run.
How to prevent it
- Never gate a build on a successful cache restore unless it truly cannot proceed.
- Cache the dependency store rather than the installed tree, so entries stay small.
- Cap matrix parallelism when every leg saves its own cache.
- Keep an eye on the 10 GB repository limit, because eviction takes the least recently used entries first.
Frequently asked questions
Is "Cache service responded with 503" a build failure?
fail-on-cache-miss, or a command that assumed the restored files were there. Find that step, because it is the one you can fix.Why does the Actions cache throttle a matrix build?
max-parallel and sharing one warm cache through restore-keys removes most of it.