Skip to content
Latchkey LogoLatchkey home

GitHub Actions Artifact not found for name on download

A GitHub Actions Artifact not found for name error is a search that came back empty: actions/download-artifact asked for artifacts matching one name and got none. The scope of that search is the whole story, because unless you pass a token it covers exactly one run in one repository.

Where download-artifact searches for an artifact, and the four ways the search comes back empty
The default search covers one run in one repository. Every input that widens it has to be set, and two of them only work together.

What this error means

A job that has nothing to do fails immediately, usually the first job downstream of a build. The step log is short: a line saying it is downloading a single artifact, the with: block echoed back, and then the failure. The text arrives on three lines and only the first names your artifact. The other two are a standing hint printed on every occurrence of this error, so they are not evidence about your run. The producing job is usually green, which is the confusing part, because a green upload step is not the same thing as an upload that happened.

Actions log, quoted from opentelemetry-js-contrib#3297
Error: Unable to download artifact(s): Artifact not found for name: compile-cache-1928
        Please ensure that your artifact is not expired and the artifact was uploaded using a compatible version of toolkit/upload-artifact.
        For more information, visit the GitHub Artifacts FAQ: https://github.com/actions/toolkit/blob/main/packages/artifact/docs/faq.md

A minimal workflow that produces it

This file is written for this page and has never been run. Both jobs are correct in isolation and the pair is broken: the matrix appends the operating system to the name, because from version 4 two legs cannot share one, and the consumer asks for the bare name. The upload succeeds and nothing in the run is called build.

.github/workflows/ci.yml (illustrative)
name: ci
on:
  push:

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - run: mkdir -p dist && echo hi > dist/out.txt
      - uses: actions/upload-artifact@v7
        with:
          name: build-${{ matrix.os }}
          path: dist

  publish:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: build

Common causes

The producing job never uploaded anything

A skipped job, a failed job, or an upload whose glob matched nothing all leave the run with no artifact. The last is the quiet one: the default behavior of an empty match is a warning, so the producing job goes green and the first red step is this one, in a different job, minutes later.

The names differ, usually by a matrix suffix

In our experience this is the single most common cause since version 4, because version 4 forced matrix legs to stop sharing one artifact name and the consuming job was often not updated to match. The download takes an exact name, not a prefix, so build will not match build-ubuntu-latest. The fix is either the full name or the pattern input, and the two behave differently on the destination directory.

The artifact belongs to a different run

Anything triggered by workflow_run, anything re-run on its own, and anything reaching across repositories is searching a run that does not hold the artifact. The default scope has no way to express that, so it quietly searches the wrong place and reports the name as missing.

The artifact expired, or was written by version 3

Both are named in the message and both are real. Retention is 90 days by default and can be shortened per upload or per repository, so re-running an old workflow can look for something deleted. Version 3 closed down on 30 January 2025, and artifacts it wrote are not readable by version 4 and later.

How to fix it

Read the run, not the message

  1. Open the run and look at its artifacts panel: it says at once whether the name is wrong or nothing was uploaded.
  2. If it is empty, the fault is in the producing job and this step is only the messenger.
  3. If it holds similar names, it is a mismatch, and the panel shows you the real ones.
  4. If the names are right, check whether the download is pointed at that run at all.

Match the name, or ask for the pattern

The corrected half of the illustrative pair above. Downloading by pattern with merge-multiple puts every matching artifact into one directory, which is what most consumers of a matrix build want. Without it, each artifact lands in its own subdirectory named after it.

.github/workflows/ci.yml, corrected (illustrative)
  publish:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v8
        with:
          path: dist
          pattern: build-*
          merge-multiple: true
      - run: ls -R dist

Make an empty upload fail where it happens

The default for an upload that matches nothing is a warning, so the producing job is green and this page is where you find out. Failing instead moves the error to the job that caused it, and to the step that has the path in front of it.

.github/workflows/ci.yml (illustrative)
      - uses: actions/upload-artifact@v7
        with:
          name: build-${{ matrix.os }}
          path: dist
          if-no-files-found: error

Reach across runs deliberately, with all three inputs

When the artifact really is in another run, the action needs to be told which one, and a token that is allowed to read it. All three inputs travel together: a token with no run id reads the current run, and a run id with no token is ignored. For a workflow_run trigger the id is on the event payload, so nothing has to be passed between workflows.

.github/workflows/publish.yml (illustrative)
      - uses: actions/download-artifact@v8
        with:
          name: build-ubuntu-latest
          github-token: ${{ secrets.ARTIFACTS_READ_TOKEN }}
          repository: ${{ github.repository }}
          run-id: ${{ github.event.workflow_run.id }}

Where the search actually looks

The error is raised in the toolkit, not in the action. getArtifact filters the run artifact list by name and throws when the result is empty, and the three-line text above is that error verbatim. The action top-level catch adds the prefix, which is why the line begins with a plural in parentheses even when you asked for one artifact.

What the search covers is set entirely by the inputs. With nothing but name it is the current run in the current repository, and it cannot see anything else. Passing github-token switches the action to the public REST path, and run-id and repository then say which run and which repository to read. The README is explicit that the token needs read access to Actions on the target.

actions/toolkit, artifact package, get-artifact.ts
if (getArtifactResp.data.artifacts.length === 0) {
  throw new ArtifactNotFoundError(
    `Artifact not found for name: ${artifactName}
  ...`
  )
}
Inputs you setWhat gets searchedWhat is needed
nameThe current run, current repositoryNothing extra
name, github-token, run-idOne named run in the current repositoryactions: read on that repository
name, github-token, run-id, repositoryOne named run in another repositoryA token with actions: read there
pattern, merge-multipleEvery matching artifact in the scope aboveNothing extra

What the message is not telling you

Two of the three lines are boilerplate. The sentence about expiry and a compatible upload version, and the link to the artifacts FAQ, are compiled into the error and printed whatever the real cause was. Read them once, then stop treating them as evidence.

Both situations are real, though. Artifacts have a retention period, 90 days by default whatever the repository visibility and settable per upload with retention-days, so a re-run of an old workflow looks for something the run no longer holds. That is what the report this page quotes guesses at, although its third re-run of a two day old run was nowhere near the default. Version compatibility is real too: uploads and downloads must use the same major generation, so an artifact written by version 3 is invisible to version 4 and later.

The phrase "Failed to download artifact" does exist in the toolkit, which is why it circulates as if it were this error. It is a debug line inside the download retry loop, printed between attempts, and it never reaches a normal log. If you are searching for it and finding nothing, that is why.

Why there is no recorded run on this page

This failure is a disagreement between two workflow files, or between a workflow file and a run that finished days ago. There is no runner state in it and no retry that changes the answer: the artifact list either contains the name or it does not. So there is no recorded log and no repair claim, only an illustrative pair of jobs, the toolkit source, and a quoted log from a public report.

How to prevent it

  • Put if-no-files-found: error on every upload whose output another job depends on.
  • Derive the artifact name from one expression used on both sides, rather than typing it twice.
  • Use pattern with merge-multiple for matrix output instead of a name per consumer.
  • Set retention-days deliberately when a workflow is expected to be re-run weeks later.

Frequently asked questions

Why does download-artifact say an artifact is not found when the upload was green?
Because a green upload step does not mean an artifact exists. If the path glob matched nothing the action logs a warning by default and the step still passes, leaving the run empty. Open the run artifacts list, and if it is empty set if-no-files-found: error on the upload.
How do I download an artifact from a different workflow run?
github-token is the switch, not run-id. run-id already defaults to the run you are in and is only read inside the token branch, so setting it alone changes nothing; the token needs actions: read on the target repository. Making download-artifact from another workflow run actually work covers the configuration and the 403 that follows.
How long do GitHub Actions artifacts last before download fails?
Ninety days by default, whatever the repository visibility, shortened for one upload with retention-days or for the whole repository in its settings. Visibility changes only the ceiling you may set: 90 days on a public repository, 400 on a private one. What an expired artifact prints depends on when you ask. While the record is still listed the lookup succeeds and the download is what fails, under the same Unable to download artifact(s) prefix but ending in Artifact has expired, with the artifact named, sized and given an id on the lines just above. Once the record has gone from the list there is nothing left to match, and you get this page's error instead. So expiry produces either message and neither proves it: the report this page quotes guessed at expiry and got the not-found text, on a run two days old.
Can download-artifact v8 read an artifact uploaded by v3?
No. Version 4 replaced the storage backend, and uploads and downloads have to use the same major generation. Version 3 was closed down on 30 January 2025, so in practice the question now only comes up in a repository with an old vendored workflow. Move both sides to the current majors at the same time rather than one at a time.

Related guides

References

A producer job and a consumer job, both metered. Latchkey runs them at $0.0025/min at 2 vCPU. Start free → 30-day trial · No credit card