Skip to content
Latchkey LogoLatchkey home

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.

The three calls an artifact upload makes and which failure shape each one produces
Read the message from the outside in. The method name says which call, and the middle clause says whether the client retried before giving up.

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.

Actions log, quoted from upload-artifact#569
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/CreateArtifact

A 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.

.github/workflows/ci.yml (illustrative)
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: error

Common 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

  1. Find the method name after the word Failed: it says which call stopped.
  2. Read the middle clause: non-retryable is one answer, five attempts is no answer.
  3. Read the status code in parentheses: 409 is a name conflict, other 4xx is your side.
  4. 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.

Terminal
gh run rerun --failed
gh run watch

Make 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.

.github/workflows/ci.yml (illustrative)
      - uses: actions/upload-artifact@v7
        with:
          name: site
          path: dist
          compression-level: 0
          retention-days: 7
          if-no-files-found: error

Fix 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.

.github/workflows/ci.yml (illustrative)
      - run: tar -cf site.tar -C dist .
      - uses: actions/upload-artifact@v7
        with:
          archive: false
          path: site.tar

Three 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 saysWhat happenedWhere the problem is
Received non-retryable errorThe service answered once and the client stoppedYour workflow or your account
Failed to make request after 5 attemptsThe client retried five times and gave upThe service or the network
Artifact storage quota has been hitChecked before the retry logicThe account storage allowance
Unable to make request and a codeThe connection never completedRunner 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-days on 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?
That the first of the three calls an upload makes did not succeed. Create comes before the file transfer, so the files were found and nothing was sent. What follows says why: a non-retryable error means the service answered once and the client stopped, most often a 409 for a name that already exists.
How many times does upload-artifact retry before it fails?
Five, with an exponential backoff between them, and only for a short list of status codes: bad gateway, gateway timeout, internal server error, service unavailable and too many requests. Everything else throws on the first answer. The attempts print as a numbered ladder, so their presence tells you which path the failure took.
What do I do about Artifact storage quota has been hit?
Reduce what is stored rather than retry: this is checked before the retry logic and will not resolve itself. Set 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?
Because direct upload sends one file unzipped and takes the artifact name from that file, so more than one match has no name to use. The action checks the count first and fails with the number it found. For a single unzipped object from many files, tar them in a previous step and upload the tar.

Related guides

References

A cache problem on Latchkey can slow a build. It cannot fail one. Start free → 30-day trial · No credit card