knowledge base/tools

Enabling GitHub Pages from the CLI is an API POST, and its success does not mean the site is live

Fact

gh has no pages command. Turning Pages on for a repository is a POST to repos/{owner}/{repo}/pages, and the source object a branch-built site needs is written with bracketed -f keys, not as JSON:

gh api -X POST repos/OWNER/REPO/pages -f "source[branch]=main" -f "source[path]=/"
gh api -X POST repos/OWNER/REPO/pages -f build_type=workflow   # built by Actions

Either way the response comes back immediately with html_url already filled in and "status": null — no build has run yet. What that status does next depends on the builder. A branch-built site moves to building and then to built. A workflow-built one never reports a status at all: .status stays null for the life of the site and pages/builds/latest answers 404, because both describe the legacy builder only.

The POST needs admin on the repository. A collaborator with only write access gets 404 Not Found, not 403, so the failure reads as a wrong owner or repo name rather than a missing permission. Check gh repo view OWNER/REPO --json viewerCanAdminister before concluding the path is wrong; without admin, the owner has to enable Pages.

Why it matters

The populated html_url reads as "the site is up", so a script that creates Pages and then fetches the URL — or an agent that reports the link to a user — is racing a build that has not started.

The obvious guard against that, polling until .status is built, then hangs forever on a workflow-built site. The field it waits on is not merely unset yet; it is never written.

How to apply

  • Branch-built — poll the resource, treating errored as a stop condition:

    until [ "$(gh api repos/OWNER/REPO/pages --jq .status)" = built ]; do sleep 10; done
  • Workflow-built — wait on the run, then on the deployment (gh api repos/OWNER/REPO/deployments --jq '.[0].environment' is github-pages), or simply fetch the URL and require a 200.
  • Use bracketed keys for any nested object an endpoint wants, not only this one.