Lossless Changelog
fleet

Three GitHub Pages gotchas, conquered

Three failed deploys in a row before the splash actually shipped. None of them were our code; all of them were GitHub Pages onboarding friction nobody warns you about. We documented every one — what failed, what fixed it, and the canonical workflow shape that survives them — so the next Lossless repo can pay this setup tax in minutes instead of an hour.

Michael StatonMusic Editoraugmented with Claude Code on Claude Opus 4.7 (1M context)

Why Care?

GitHub Pages markets itself as "free static hosting from your repo." The page that explains how to use it makes it look like four clicks and you're done. The reality, on a brand-new repo, is that there are at least three independent first-time gotchas — each producing a different error message that looks like a different problem but isn't.

If you've ever stared at a red ❌ next to "Deploy splash to GitHub Pages" and wondered whether the workflow was wrong, the repo was wrong, or you yourself were wrong, this entry is for you. None of those. It's GitHub.

This is also the entry that turns the gotchas into a playbook the next Lossless repo gets for free. Captured at astro-knots/references/playbooks/github-pages-deploy.md in the agent skill tree.

What's New?

  • One-line workflow fix landed that makes the workflow self-bootstrap: enablement: true on actions/configure-pages@v5. Without it, the very first run on a brand-new repo fails with Get Pages site failed ... Not Found.

  • Build runtime bumped from Node 20 to Node 22 ahead of GitHub's September 2026 deprecation cutoff. Removes a recurring deprecation warning on every run.

  • Three setup-time gotchas documented as a playbook in the astro-knots skill, with order-of-operations and the exact UI paths to fix each.

  • Splash is queued for its first successful deploy at lossless-group.github.io/content-farm/. Failed three times before we got the configuration right; the latest run, triggered after all three were addressed, should succeed.

The three gotchas (in the order they bite)

Gotcha 1: First-run 404 from actions/configure-pages

The error:

Error: Get Pages site failed. Please verify that the repository has Pages
enabled and configured to build using GitHub Actions, or consider exploring
the `enablement` parameter for this action.
Error: Not Found - https://docs.github.com/rest/pages/pages#get-a-apiname-pages-site
Error: HttpError: Not Found

The cause: actions/configure-pages queries GET /repos/{owner}/{repo}/pages to find the existing Pages site. On a brand-new repo, that resource doesn't exist yet. The 404 propagates as a hard failure.

The fix — one line:

DIFF
       - name: Configure Pages
         uses: actions/configure-pages@v5
+        with:
+          # Auto-enable Pages on first run so the workflow bootstraps itself
+          # even when "Get Pages site" returns 404 the very first time.
+          enablement: true

enablement: true makes the action POST to provision the Pages site if the GET 404s, using the pages: write permission already declared on the workflow. On second and subsequent runs, it just uses the existing site — no harm.

This was the original ticket-zero gotcha. Once enablement: true was in, the build job started succeeding. Then the deploy job started failing — for an entirely different reason.

Gotcha 2: Branch not allowed to deploy

The error:

Branch "main" is not allowed to deploy to github-pages due to environment
protection rules.
The deployment was rejected or didn't satisfy other protection rules.

The cause: GitHub auto-creates a github-pages environment the first time a Pages workflow runs. That environment has a default deployment-branch rule that limits deploys to the repository's default branch. content-farm's default branch was still master from when the repo was initialized; we'd just created main to match the dev → main → master tier model and were deploying from it. The environment didn't agree.

Two ways to fix it. Both work.

(A) Allow main in the environment's deployment-branch policy. UI path: Settings → Environments → github-pages → "Deployment branches and tags" → "Add deployment branch or tag rule" → enter main → Add. Or via the API:

BASH
gh api -X POST repos/lossless-group/content-farm/environments/github-pages/deployment-branch-policies \
  -f name=main -f type=branch

After this fix, the branch_policies for the env contained both:

JSON
{
  "total_count": 2,
  "branch_policies": [
    { "id": 48640893, "name": "main", "type": "branch" },
    { "id": 48640114, "name": "master", "type": "branch" }
  ]
}

(B) Change the repo's default branch to main. Settings → Branches → switch default. More invasive — affects PR base branches, branch protection rules, contributor expectations everywhere. We took option B for content-farm because aligning with the tier model was overdue anyway. For most other repos in the family, option A is the lighter touch.

After this fix, the rejection message should disappear on subsequent runs.

Gotcha 3: Source still set to "Deploy from a branch"

The error: No error per se — but the workflow runs to completion, and lossless-group.github.io/content-farm/ either 404s or shows a Jekyll-built blank page. Settings → Pages shows a Branch dropdown with master selected, a Folder dropdown with / (root) or /docs, and a polite suggestion to "add a Jekyll theme."

The cause: Pages has two completely different deployment modes. Both are reached from Settings → Pages, but they use different infrastructure:

ModeWhat it does
Deploy from a branchTreats the chosen folder of the chosen branch as a Jekyll site. Builds and serves it.
GitHub ActionsReceives an artifact from a workflow's actions/upload-pages-artifact step and serves it.

If Source is set to "Deploy from a branch," your workflow's deploy-pages step has nothing to deploy to — Pages is busy doing its own Jekyll build and ignoring your artifact upload entirely.

The fix: Settings → Pages → "Build and deployment" → Source dropdown → change from "Deploy from a branch" to "GitHub Actions". The Branch and Folder pickers vanish; the Jekyll suggestion vanishes; a workflow-status panel takes their place.

This one is the sneakiest of the three because the legacy-mode UI sits exactly where you'd expect the Actions-mode UI to sit. Easy to miss the dropdown.

The Node 20 deprecation, while we were here

GitHub Actions runners are in the middle of deprecating Node 20. Every workflow run produced this warning:

Node.js 20 actions are deprecated. The following actions are running on
Node.js 20 and may not work as expected: actions/checkout@v4,
actions/configure-pages@v5, actions/setup-node@v4, actions/upload-artifact@v4,
pnpm/action-setup@v4. Actions will be forced to run with Node.js 24 by default
starting June 2nd, 2026. Node.js 20 will be removed from the runner on
September 16th, 2026.

Two layers there:

  • The actions themselves (checkout, configure-pages, etc.) ship Node 20 internally. Resolving that requires the action maintainers to publish updated tags. Out of our hands; nothing to fix locally.

  • The Node version our build uses (the node-version: 20 in our setup-node step). That one we control. Bumped to 22 LTS:

DIFF
       - name: Setup Node
         uses: actions/setup-node@v4
         with:
-          node-version: 20
+          node-version: 22
           cache: pnpm
           cache-dependency-path: splash/pnpm-lock.yaml

Node 22 is the current LTS as of this push. Node 24 is the next LTS but isn't out long enough yet to be the default everywhere.

The canonical workflow, post-fixes

The full state of .github/workflows/pages.yml after this round, for reference and for any future Lossless repo that wants the recipe:

YAML
name: Deploy splash to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: splash
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive       # crucial when content rolls up via the Content API
          fetch-depth: 1

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 22            # bumped from 20 ahead of deprecation
          cache: pnpm
          cache-dependency-path: splash/pnpm-lock.yaml

      - run: pnpm install --frozen-lockfile=false
      - run: pnpm build

      - uses: actions/configure-pages@v5
        with:
          enablement: true            # self-bootstrap on first run

      - uses: actions/upload-pages-artifact@v3
        with:
          path: splash/dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

Three things to notice if you're copying this:

  1. defaults.run.working-directory: splash — required because the Astro app lives at a subpath. The cache-dependency-path and the artifact path use the absolute-from-repo-root path; everything else is relative to splash/.

  2. enablement: true on configure-pages — Gotcha 1 fix, in the workflow itself.

  3. submodules: recursive on checkout — needed even before the roll-up loader lands, because the splash's content collections currently glob into ../context-v and that pulls in spec/plan files that may reference submodule context.

What got captured outside the repo

The three gotchas above are now a playbook in the astro-knots skill at references/playbooks/github-pages-deploy.md. The playbook covers:

  • The full canonical workflow shape (above)

  • The astro.config.mjs base setting that has to match the repo path

  • Each gotcha with symptom, cause, fix

  • A 6-step setup checklist in the order each gotcha bites

  • Verification steps

  • Cross-references to the new-site-setup playbook (Vercel default) and the content-rollup reference

Linked from the astro-knots SKILL.md in two places — the "Initiating a new Astro-Knots project" section and the "See also" list — so it's reachable from any Astro Knots task.

The honest middle

The first three workflow runs (commit hashes 61dfe40, 37fba2c, 9d504cc) all failed on main. The screenshot of the Actions tab at one point looked like:

3 workflow runs

❌ fix(pages-workflow): self-bootstrap GitHub Pages on first run    main
❌ seed(roll-up): bump submodule pointers...                         main
❌ ship(splash): launch the content-farm GitHub Pages splash...      main

Each failure was a different one of the three gotchas. The first two failed at Gotcha 1 (no enablement: true). The third pushed the enablement: true fix and got past it, then failed at Gotcha 2 (env protection on main). After fixing the default branch and re-triggering manually via gh workflow run, that finally got past Gotcha 2 — and then we noticed Gotcha 3 (Source on "Deploy from a branch") in the Pages settings, which had been sitting there silently the whole time. Flipped it. The most-recent run as of this entry's push is queued at https://github.com/lossless-group/content-farm/actions/runs/25356485378 and should succeed.

If you're feeling demoralized, three failed deploys is cheap compared to writing this playbook before doing the work. The playbook costs nothing to read and saves the next person from doing the live debug.

What's next

  • Watch the latest run. If it succeeds, the splash goes live. If it fails again, the env policy may need to be deleted and recreated (the API said both branches were allowed but the rule still blocked deploys at one point — flagged as the only remaining unknown).

  • Roll-up loader still next on the spec. Three gotchas of GitHub Pages onboarding doesn't change Phase 2 priorities.

  • Workflow re-use. When the next Lossless repo wants its splash, this pages.yml is the template. Copy verbatim, change splash to the relevant subpath, change [main] to the desired publication branch.