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.

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.
tar: _site: Cannot open: No such file or directory
tar: Error is not recoverable: exiting nowWhat 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.
- 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.
- run: npm run build
- run: ls -la ./dist
- uses: actions/upload-pages-artifact@v5
with:
path: ./distBuild and upload in the same job
- Keep the build step and the upload step in one job so they share a working directory.
- Put the deploy in a second job with
needs:pointing at the first. - Leave the checkout in the build job only; the deploy job does not need your source.
- 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.
- uses: actions/upload-pages-artifact@v5
with:
path: ./dist
name: docs-site
- uses: actions/deploy-pages@v5
with:
artifact_name: docs-siteRaise 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.
- uses: actions/upload-pages-artifact@v5
with:
path: ./dist
retention-days: 7Four 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.
| Input | Default | What a wrong value produces |
|---|---|---|
path | _site/ | tar stops with Cannot open, before any upload |
name | github-pages | the upload succeeds and deploy-pages finds nothing |
retention-days | 1 | a re-run a day later has no artifact to deploy |
include-hidden-files | false | dotfiles 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
pathexplicitly, 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-erroron a build step that a later step depends on. - Leave
namealone unless a run deploys more than one site.
Frequently asked questions
What is the default path for actions/upload-pages-artifact?
_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?
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?
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?
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
- actions/upload-pages-artifact action.yml: the four inputs, their defaults and the three tar steps
- actions/deploy-pages action.yml: the artifact_name input this upload has to agree with
- GitHub Pages: publishing with a custom GitHub Actions workflow
- actions/upload-artifact: the version the composite pins and the if-no-files-found behavior
- GitHub Actions documentation