GitHub Actions artifact upload failed after the files were found
When a GitHub Actions artifact upload failed and the log above already said how many files it was going to send, the glob is not the problem. The action got past its own checks and one of three service calls did not come back, and the shape of the failure line says which call it was.

What this error means
The step runs for a while, which is the first difference from an empty match: there are real files and a real archive. Above the failure the log reads like success, with a count of files and two lines saying the name and the root directory are valid. Then either a ladder of numbered attempts, or nothing at all. The two shapes mean opposite things: a ladder means the client kept retrying, either because nothing came back or because what came back was on its retryable list, and no ladder means the first answer was one it refuses to retry.
With the provided path, there will be 9 files uploaded
Artifact name is valid!
Root directory input is valid!
Attempt 1 of 5 failed with error: Request timeout: /twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact. Retrying request in 3000 ms...
Attempt 2 of 5 failed with error: Request timeout: /twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact. Retrying request in 4541 ms...
##[error]Failed to CreateArtifact: Failed to make request after 5 attempts: Request timeout: /twirp/github.actions.results.api.v1.ArtifactService/CreateArtifactA minimal workflow that reaches this point
This file is written for this page and has never been run. Unlike an empty match there is nothing wrong with it: the path is right and the upload normally succeeds. It is here because this failure happens to correct workflows. The log above comes from a workflow of this shape, with timestamps removed and three of the five attempts left out for length.
name: ci
on:
push:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: npm ci && npm run build
- uses: actions/upload-artifact@v7
with:
name: site
path: dist
if-no-files-found: errorCommon causes
The service answered with something the client will not retry
The status code is in the line, in parentheses, and it is the fastest thing to read. A 409 is a name that already exists in the run, the most common member of this group since artifacts became immutable. A 403 or a 401 points at the job token instead, usually a workflow triggered where the token is read-only.
The service never answered and the client gave up
Five attempts with exponential backoff, then the failure. The report this page quotes is exactly this: nine files found, every validation passed, and five timeouts against the create endpoint inside a minute. It is intermittent by nature and clusters when the service is degraded, so the status page is more useful than the workflow file.
The account is out of artifact storage
Special-cased ahead of the retry logic, with a link to the billing documentation. Storage is recalculated on a delay, so a repository that has just deleted a lot of artifacts can keep failing after the cleanup looks finished. Retention settings are the durable fix, per upload as well as per repository.
The upload was rejected before it was sent
An invalid character in the artifact name, a direct upload pointed at a glob matching several files, or GitHub Enterprise Server, where version 4 and later are not supported. All three produce text with no method name, so if the line does not say which call failed, no call happened.
How to fix it
Place the failure before you change anything
- Find the method name after the word Failed: it says which call stopped.
- Read the middle clause: non-retryable is one answer, five attempts is no answer.
- Read the status code in parentheses: 409 is a name conflict, other 4xx is your side.
- If there is no method name, no request was made and the inputs are at fault.
Re-run the job when the ladder is there, and nothing else
For the retry-exhaustion shape the only sensible first move is to run it again, because the client has already exhausted every retry available inside the step. If it recurs across several runs, check the GitHub status page before changing a workflow.
gh run rerun --failed
gh run watchMake a large upload smaller rather than slower
A directory that is mostly incompressible, such as prebuilt binaries or images, spends time in the zip stage for no gain. The README says as much: on random binary data, opting out of compression completely saves a lot of time because it will not benefit, while a higher level saves space on text at the cost of CPU. The compression level is an input, and narrowing the path helps more than either.
- uses: actions/upload-artifact@v7
with:
name: site
path: dist
compression-level: 0
retention-days: 7
if-no-files-found: errorFix the inputs when nothing was ever sent
For a rejected name, take the slashes out: a name is not a path. For a direct upload, point it at one file rather than a directory, which is also the documented way to keep file permissions, because a tar uploaded unzipped survives the download intact.
- run: tar -cf site.tar -C dist .
- uses: actions/upload-artifact@v7
with:
archive: false
path: site.tarThree calls, and a client that only retries some answers
Once the files are found the toolkit does three things: it asks the service to create an artifact, it streams the archive to the storage URL that call returns, and it asks the service to finalize what was uploaded. The first and third go through a small typed client, and every failure message is assembled by that client out of four fragments.
The outermost fragment names the call, so Failed to CreateArtifact tells you which end stopped. The next one is the important one. Received non-retryable error means the client read the status code and stopped on the first answer. Failed to make request after 5 attempts means the opposite: it backed off, tried five times, and the last answer was no better.
What counts as retryable is a short list in the source: bad gateway, gateway timeout, internal server error, service unavailable and too many requests. Everything else throws immediately. Two failures skip the ladder entirely: an exhausted storage quota, which no retry clears, and a connection-level error, raised for five codes including ECONNRESET and ETIMEDOUT and reported with a link to the endpoints self-hosted runners must reach. That one can be transient in itself; what it is not is retried inside the step.
| What the line says | What happened | Where the problem is |
|---|---|---|
Received non-retryable error | The service answered once and the client stopped | Your workflow or your account |
Failed to make request after 5 attempts | The client retried five times and gave up | The service or the network |
| Artifact storage quota has been hit | Checked before the retry logic | The account storage allowance |
Unable to make request and a code | The connection never completed | Runner egress or a proxy |
The failures that happen before any call
Not everything here is a service problem. Three checks run between the glob and the first request, and each raises text with no method name in it. The artifact name is validated against a fixed character list, so a name holding a slash, a colon, an asterisk or a newline is rejected with a message naming the character.
The second is new in version 7, which added direct uploads: with archive: false a single file is sent unzipped and the artifact takes the name of the file rather than the name input. Point that at a glob matching more than one file and the action fails at once, naming the count it found. The third is platform: version 4 and later are not supported on GitHub Enterprise Server, and the toolkit carries a dedicated error saying so.
One limit sits apart because it is documented rather than validated: the README states a ceiling of 500 artifacts created within a single job. A matrix uploading per test shard can reach it.
Why there is no recorded run on this page
One member of this family really is transient, so the absence of a recorded run is worth explaining rather than asserting. The retry ladder is a genuine service failure and re-running the job often succeeds. But it happens inside the GitHub artifact service, behind a client that has already tried five times before the line you are reading was written, so nothing is left on the runner to retry and nothing a managed runner could repair for you. Everything else here is a workflow or account fact that does not change on a second attempt.
How to prevent it
- Keep artifact payloads narrow: upload what a later job reads, not the whole build directory.
- Set
retention-dayson large uploads so storage is reclaimed without a cleanup job. - Keep artifact names free of slashes, colons and anything else a file system would reject.
- Treat a single retry-exhaustion failure as weather, and a pattern of them as an incident.
Frequently asked questions
What does Failed to CreateArtifact mean in GitHub Actions?
How many times does upload-artifact retry before it fails?
What do I do about Artifact storage quota has been hit?
retention-days on uploads that need not outlive the week, delete old artifacts, and remember that storage is recalculated on a delay, so uploads can keep failing after a cleanup.Why does archive: false fail when my path matches several files?
Related guides
References
- A report with the retry ladder and the exhausted-attempts failure in full
- actions/toolkit: the artifact client that assembles these messages
- actions/upload-artifact README: inputs, the artifact limit and permissions
- GitHub Actions: store and share data with workflow artifacts
- GitHub Actions documentation