Skip to content
Latchkey LogoLatchkey home

When actions/upload-pages-artifact path does not exist

When an actions/upload-pages-artifact path does not exist, the step that goes red is tar, not an upload. The action is a composite of four steps, and knowing which one failed tells you whether your site was never built or was built somewhere else.

The four composite steps, the input each one reads, and which failure each produces
upload-pages-artifact is a composite of four steps. Three of them are tar, gated on runner.os, and only the fourth is an upload.

What this error means

The Pages build job stops on a step called Upload artifact, or on one called Archive artifact just above it, and the two lines in the log are from tar rather than from anything that looks like a GitHub action. There is no annotation naming an artifact and no mention of Pages at all. Further down the run, if the deploy job was allowed to start, a second and very different failure appears in actions/deploy-pages, which looks for an artifact that was never created. People usually read the second one first because it is the one that mentions Pages, and then spend the afternoon on the wrong job.

Reconstructed: the composite action's own tar invocation run against a missing _site with GNU tar 1.35, in an ubuntu:24.04 container. Not captured from an Actions run
tar: _site: Cannot open: No such file or directory
tar: Error is not recoverable: exiting now

What the action actually runs

This is the Linux branch of the composite, quoted from the action metadata. It is a plain shell step. INPUT_PATH is the path input, which is required and defaults to _site/, and --directory makes tar change into it before archiving. If that directory is not there, tar reports it and exits 2, and the composite stops before the upload step is reached.

There are three copies of this step, gated on runner.os. macOS runs gtar instead, because the default tar on macOS is bsdtar and does not take --hard-dereference. Windows adds --force-local and a quoted ".". The failure reads slightly differently on each, which is worth knowing if you are matching log text across a matrix.

actions/upload-pages-artifact, action.yml (v5.0.0)
- name: Archive artifact
  shell: sh
  if: runner.os == 'Linux'
  run: |
    echo ::group::Archive artifact
    tar \
      --dereference --hard-dereference \
      --directory "$INPUT_PATH" \
      -cvf "$RUNNER_TEMP/artifact.tar" \
      --exclude=.git \
      --exclude=.github \
      ${{ inputs.include-hidden-files != 'true' && '--exclude=.[^/]*' || '' }} \
      .
    echo ::endgroup::
  env:
    INPUT_PATH: ${{ inputs.path }}

Common causes

Your build writes somewhere other than _site

The path default is a Jekyll convention, and almost nothing else uses it. Vite writes dist, Next.js static export writes out, Astro writes dist, Hugo writes public. The action does not look for any of them, so the first run of a hand-written Pages workflow on a modern site generator lands here.

The build step did not run, or ran in another job

Artifacts are scoped to the run, but the working directory is scoped to the job. A build in one job and an upload in another leaves the second job with a clean checkout and no build output, so tar finds nothing even though the path in the workflow is right.

The build failed but the workflow kept going

A build step with continue-on-error: true, or a script that swallows a non-zero exit, leaves the job green and the output directory absent. The archive step is then the first place the failure becomes visible, which makes it look like an artifact problem rather than a build one.

The path is right but the artifact name is not

This one does not produce the tar error at all. The upload is green and the failure moves to the deploy job, because actions/deploy-pages looks for an artifact called github-pages and you called it something else. In our experience this is the one people reach for last, having already checked the path three times.

How to fix it

Point path at what your generator actually writes

Set path explicitly rather than relying on the default, even when the default happens to be correct, so the next person does not have to know what _site means. Add a listing step before the upload while you are debugging; it costs nothing and it settles the question of whether the directory exists in this job.

.github/workflows/pages.yml (illustrative)
      - run: npm run build
      - run: ls -la ./dist
      - uses: actions/upload-pages-artifact@v5
        with:
          path: ./dist

Build and upload in the same job

  1. Keep the build step and the upload step in one job so they share a working directory.
  2. Put the deploy in a second job with needs: pointing at the first.
  3. Leave the checkout in the build job only; the deploy job does not need your source.
  4. If the build genuinely has to live elsewhere, pass its output through a normal artifact and unpack it before the Pages upload.

Set the name on both sides or on neither

If you have a reason to rename the artifact, for example because a run deploys two sites, set name on the upload and artifact_name on the deploy to the same value. If you do not have that reason, delete both and let the defaults agree with each other.

.github/workflows/pages.yml (illustrative)
      - uses: actions/upload-pages-artifact@v5
        with:
          path: ./dist
          name: docs-site

      - uses: actions/deploy-pages@v5
        with:
          artifact_name: docs-site

Raise retention only if you re-run old deploys

The one-day default is right for a pipeline that deploys immediately. If you rely on re-running a Pages workflow from the runs list days later, raise retention-days on the upload, and remember that the repository maximum applies. Re-running the whole workflow rebuilds the artifact and is usually the better answer.

.github/workflows/pages.yml (illustrative)
      - uses: actions/upload-pages-artifact@v5
        with:
          path: ./dist
          retention-days: 7

Four inputs, and what a wrong value costs

The action has four inputs and every one of them has a default that is easy to outgrow. The defaults are chosen for a Jekyll build, which is why a Vite or Next.js project that emits into dist or out hits this on its first run.

The retention-days default is the one nobody expects. It is 1, not the ninety days an ordinary artifact gets. That is deliberate, since a Pages artifact is consumed minutes later by the deploy job, but it means a Pages run you re-run the next morning cannot find its own artifact any more.

InputDefaultWhat a wrong value produces
path_site/tar stops with Cannot open, before any upload
namegithub-pagesthe upload succeeds and deploy-pages finds nothing
retention-days1a re-run a day later has no artifact to deploy
include-hidden-filesfalsedotfiles are excluded from the archive, silently

The upload step is pinned, and that matters

The last step of the composite is actions/upload-artifact, pinned by commit SHA to version 7.0.0, with if-no-files-found: error set. Two things follow from that. You cannot change the upload behavior from your workflow, because the inputs of the composite do not expose it; and the artifact that reaches the Pages service is an ordinary Actions artifact containing exactly one file, artifact.tar.

It also means the empty-directory case behaves differently from the missing-directory case. A path that exists but has nothing in it gives tar an archive with one entry, ./, which uploads cleanly and then deploys a site with no pages in it. No step goes red anywhere in the run. If your deploy is green and your site is a 404, this is the first thing to check.

Changing name is the other quiet failure. The upload step takes whatever you pass, and actions/deploy-pages looks for its own artifact_name, which also defaults to github-pages. Set the name on one side only and the upload is green, the deploy is red, and the two steps are in different jobs.

Why there is no recorded run on this page

A log from a Pages run would not add anything here, because the interesting output is not produced by a GitHub action at all. It is produced by tar, and tar is the same program on the runner as it is anywhere else. The two lines quoted above were produced by running the composite own invocation, verbatim, against a missing _site under GNU tar 1.35, which is the version in the Ubuntu runner image. Wrapping that in a workflow would change the indentation and nothing else.

The failure is also not something a runner can repair. The directory is absent because a build step did not write it, and retrying the archive step retries a decision that was already made one step earlier.

How to prevent it

  • Set path explicitly, and keep it next to the build command that produces it.
  • Keep build and upload in one job, deploy in a second job with needs:.
  • Never set continue-on-error on a build step that a later step depends on.
  • Leave name alone unless a run deploys more than one site.

Frequently asked questions

What is the default path for actions/upload-pages-artifact?
It is _site/, which is where Jekyll writes. The input is marked required and carries that default, so omitting it does not disable it. Any generator that writes to dist, out or public needs the input set explicitly or the archive step cannot change into the directory.
Why does upload-pages-artifact fail with a tar error?
Because the first three steps of the composite are literally tar. The action changes into your path with --directory before archiving, so a missing directory is reported by tar rather than by anything Pages-aware. Exit code 2 from tar stops the composite before the upload step runs.
How long does a GitHub Pages artifact last?
One day by default. The action sets retention-days to 1, not to the ninety days a normal artifact gets, because the deploy job consumes it within minutes. Re-running a Pages workflow the next day will not find it, and you should re-run the build rather than raise the retention.
Can I use actions/upload-artifact instead for Pages?
Not directly. The Pages deployment expects a single artifact.tar produced the way the composite produces it, with --dereference --hard-dereference and the .git and .github exclusions. Uploading your site directory with the generic action gives you a zip of loose files that the deployment cannot use.

Related guides

References

A Pages build spends longer installing than building. Latchkey Fast Cache is a one-line swap. Start free → 30-day trial · No credit card