Skip to content
Latchkey LogoLatchkey home

actions/deploy-pages deployment failed on the github-pages environment

An actions/deploy-pages deployment failed at one of three gates, and the line in the log tells you which. Two of them reject the ref you are deploying from and one of them rejects what you uploaded, so the fix is a repository setting in two cases out of three and a rebuild in the third.

Three gates a Pages deploy passes, and the different line each one prints when it refuses
Gate one leaves no step log. Quoted from the deployments reference: "All deployment protection rules must pass before a job referencing the environment is sent to a runner."

What this error means

The build job is green and the deploy job is not. Which gate stopped it is visible from how much of the deploy job ran. No steps at all, with the failure as an annotation on the job itself, means the environment refused the ref before the job reached a runner. A deploy step that ran and failed while creating the deployment means the Pages API refused the call, and the action quotes its response. A step that created the deployment and then failed while polling means the deployment reached a terminal state, and the action prints one fixed sentence for it. Three gates, three repairs, and the first leaves no step log at all, which is why it gets mistaken for a broken action.

Job annotation, quoted from actions/deploy-pages#151
Branch "v1.1.2" is not allowed to deploy to github-pages due to environment protection rules.

Gate one: the environment refuses the ref

A job with environment: github-pages is checked against that environment before it is scheduled. GitHub's reference is unambiguous about the ordering: "All deployment protection rules must pass before a job referencing the environment is sent to a runner." When the deployment branch and tag rule does not match, the job fails as an annotation with the line quoted above and no step ever runs. Nothing in actions/deploy-pages produced that message, so reading its source or bumping its version will not move it.

The rule is matched against the run's ref, not against your branch name in the abstract: "The deployment branch or tag rule is matched against the GITHUB_REF of the workflow run." The options GitHub documents are "No restriction", "Protected branches only", and "Selected branches and tags", and the third is where tags trip people up. A release-triggered deploy has a tag ref, so a policy that lists main rejects it. GitHub also warns that "Deployment workflow runs triggered by tags with the same name as a protected branch and forks with branches that match the protected branch name cannot deploy to the environment."

Common causes

The github-pages environment does not allow the ref

The loudest of the three and the one with no step log. It fires on a tag, on a release-triggered run, and on any ref that does not match the environment's deployment branch and tag rule. Because the failure is an annotation on the job rather than a step, the run looks like the action never started, which it did not.

The Pages source is still a branch

A repository that published from gh-pages before it moved to a workflow keeps that setting until someone changes it. The API refuses the create call with a 400 and says which branch it will accept, and the action passes that sentence through. In our experience it is the most common 400 on a repository that used to deploy a Jekyll site.

The deployment reached a terminal status after it was created

The create call succeeded, so permissions and the ref were both fine, and the deployment failed on the Pages side. The sentence in the log names which terminal status it was, and only deployment_content_failed points at your artifact rather than at the service.

The artifact carries content Pages will not publish

Hard links, symlinks and total size are the three the action names. A build that copies a tree with cp -al, or one that ships a full node_modules, produces exactly this. It is the only cause here whose fix is in the build job.

How to fix it

Work out which gate stopped the deploy before changing anything

  1. Open the deploy job. If it has no steps, the environment refused the ref and the answer is in the environment settings.
  2. If the deploy step ran and the message starts Failed to create deployment, the API refused the call and the status code in that line is the diagnosis.
  3. If the step created a deployment and then printed one of the fixed sentences above, the deployment failed after creation and the sentence names which way.

Allow the ref you are actually deploying from

In the repository settings, open the github-pages environment and look at deployment branches and tags. Either choose no restriction, or add a pattern that matches the run's ref. Remember the matching is against GITHUB_REF, so a workflow triggered by a release needs a tag pattern and not a branch name. GitHub notes that wildcards will not match a slash, so a pattern of release/* does not cover release/2026/09.

Switch the Pages source to GitHub Actions

This is the repair for the 400 that names a branch. In the repository's Pages settings, set the build and deployment source to GitHub Actions. Until that is done, the API will keep telling you which branch it expects, no matter what the workflow file says.

.github/workflows/pages.yml (illustrative)
  deploy:
    needs: build
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - id: deployment
        uses: actions/deploy-pages@v5

Fix the artifact when the status is deployment_content_failed

  1. Check the upload step for symlinks and hard links; find <dir> -type l and find <dir> -links +1 -type f both print quickly.
  2. Check the size of the directory you are uploading, and compare it against the warning line the action prints before it creates the deployment.
  3. Rebuild, re-upload, and re-run the deploy job on its own rather than the whole workflow.

Gate two: the Pages API refuses the create call

If the job did start and the deploy step ran, the next gate is POST /repos/{owner}/{repo}/pages/deployments. The action logs Creating Pages deployment failed from src/internal/api-client.js and then builds a message from the status code. For a 400 it appends the API's own words, which is how this shape reads in a real run:

Actions log, quoted from actions/deploy-pages#108
Error: Failed to create deployment (status: 400) with build version efba898182be4068f5a83d356f1a5bf241229025. Responded with: Invalid deployment branch and no branch protection rules set in the environment. Deployments are only allowed from gh-pages

Gate three: the deployment is created and then reports failure

Once the deployment exists, the action polls GET /repos/{owner}/{repo}/pages/deployments/{deploymentId} and reads a status string. src/internal/deployment.js holds two maps of those strings to sentences, one it treats as terminal and one it treats as temporary, and the sentence in your log tells you which map it came from. This is the table to read when the deploy step ran for a while and then gave up.

Status from the APISentence the action printsWhat the action does next
deployment_failedDeployment failed, try again later.Fails the step
deployment_content_failedArtifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.Fails the step
deployment_cancelledDeployment cancelled.Fails the step
deployment_lostDeployment failed to report final status.Fails the step
deployment_attempt_errorDeployment temporarily failed, a retry will be automatically scheduled...Warns and keeps polling
not_foundDeployment not found.Warns and keeps polling
unknown_statusUnable to get deployment status.Warns and keeps polling

The content gate is about the artifact, not the ref

The deployment_content_failed sentence is the odd one in that table, because it names something you fix in the build rather than in a setting. The action warns before it gets there: its create method checks the artifact size against one gigabyte and logs that the uploaded size "exceeds the allowed size of 1 GB. Deployment might fail." The gigabyte in that warning and the ten in the terminal sentence are two thresholds rather than a contradiction, and actions/upload-pages-artifact states both: "The GitHub Pages officially supported maximum size limit is 1GB, so the subsequent deployment of larger tarballs are not guaranteed to succeed", and "there is also an unofficial absolute maximum size limit of 10GB, which Pages will not even attempt to deploy". GitHub's published Pages limits carry the same supported figure and the mechanism behind it: a site "may be no larger than 1 GB", and deployments "will timeout if they take longer than 10 minutes". So one gigabyte is the limit, stated softly because a larger site sometimes still lands inside the timeout, and ten is where Pages stops trying. The repair is in whatever produced the site: a symlink into node_modules, a hard link left by a copy step, or an output larger than you think. Environment settings will not move it, because this gate is downstream of both ref checks.

Why there is no recorded run on this page

Gate one never reaches a runner, so there is no job to record. Gate two and gate three are answers from the Pages service about a specific repository's settings and a specific artifact, and a run of ours on our own repository would only prove that our settings are configured, which is not the reader's question. Both log excerpts here are quoted from issues on the action's own repository, and the mapping table is read from the action's source rather than reproduced.

How to prevent it

  • Decide once whether the github-pages environment restricts refs, and write the pattern for the trigger you actually use.
  • Move the Pages source to GitHub Actions the day you add the workflow, not the day the deploy first fails.
  • Keep the upload step pointed at a build output directory, never at the repository root.
  • Pin actions/deploy-pages to a major tag and read its release notes, since the status strings and messages live in its source.

Frequently asked questions

Why does my deploy job fail with no steps at all?
Because the environment refused the ref before the job was scheduled. GitHub documents that all deployment protection rules must pass before a job referencing the environment is sent to a runner, so a failed rule produces an annotation on the job and no step log. The message names the branch or tag and the environment it was refused from.
What does "Deployments are only allowed from gh-pages" mean?
It is the Pages API refusing the create call because the repository still publishes from a source branch. It arrives inside a Failed to create deployment (status: 400) line, with the API response quoted after Responded with:. The fix is in the repository Pages settings, where the build and deployment source becomes GitHub Actions.
Is "Deployment failed, try again later." worth retrying?
Only once. The action already keeps polling on its own for the three statuses it treats as temporary, deployment_attempt_error, not_found and unknown_status, the first of which prints Deployment temporarily failed, a retry will be automatically scheduled... instead. Deployment failed, try again later. comes from the map of statuses the action treats as final, so it has stopped polling deliberately.
Can I deploy Pages from a tag or a release?
Yes, but the environment has to allow it. The deployment branch and tag rule is matched against the run's GITHUB_REF, and a release-triggered run carries a tag ref. Add a tag pattern to the github-pages environment, or choose no restriction, and keep in mind that a wildcard will not match a slash.

Related guides

References

Your docs deploy runs on every merge. Latchkey runs those jobs at $0.0025/min at 2 vCPU against $0.006. Start free → 30-day trial · No credit card