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
erroredas 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'isgithub-pages), or simply fetch the URL and require a 200. - Use bracketed keys for any nested object an endpoint wants, not only this one.