← Cycles
Design · c49

Fix the Cloudflare Pages Deploy

archivedcreated 2026-06-11· last activity 2026-09-30· mode: Rebellion: dropped CI builds rather than repairing them, and replaced them with a laptop build plus a branch that Cloudflare serves as-is

Make `push to main` reliably deploy the design site to Cloudflare Pages *(2026-09-25: reframed as "make the build a manual local step", see PICKUP.md)*

Why

The design site (design.internal.qpoint.io) stopped reflecting pushes to main, and the live site drifted into a stale state (component directory updated, but new q-nuxt-layer demo pages hang on click — JS-chunk 404s).

The site is a Cloudflare Pages Direct-Upload project (qpoint-design, not Git-connected), so a push only deploys via the GitHub Action .github/workflows/deploy.yml (npm ci → npm run generate → wrangler pages deploy). That Action was created 2026-04-13 and has failed all 8 runs, never once reaching the Cloudflare step. With auto-deploy broken, deploys happened via manual/partial wrangler pages deploy, which is what left the live site with mismatched assets.

We initially suspected qpixel introduced the break. The evidence disproves that — the Action failed on its first run (2026-04-13) with an npm-auth 401, three days before qpixel was added (2026-04-16). It was three independent misconfigurations stacked, each hiding the next:

#MisconfigurationIntroducedStatus
1npm auth: .npmrc reads ${GITHUB_TOKEN} but the Action fed the auto repo token (can't read sibling @qpoint-io/* packages) → 4012026-04-13✅ Fixed (6f41e1d, 5e31670)
2qpixel dep file:../qpixel/…tgz (sibling path absent on CI) → ENOENT2026-04-16✅ Fixed — vendored to design/vendor/ (97f27d4)
3Cloudflare creds (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID) never set, never testednever⛔ Open — the only remaining blocker

Objective

push to main reliably produces a clean, complete Cloudflare Pages deploy — ending the manual/partial deploys that leave the live site with stale, hanging pages. Proven when two consecutive pushes each auto-deploy green and the previously-hanging demo pages load.

Scope

  • In: Add the two Cloudflare secrets; trigger and verify the never-before-run wrangler step; confirm the clean deploy clears the live stale-asset hang.
  • In (fallback only): swap cloudflare/wrangler-action@v3 for a direct npx wrangler@4 pages deploy CLI call if the action misbehaves on Node 20.
  • Out: switching to Cloudflare Git integration (would just relocate the same auth + qpixel problems into Cloudflare's builder). qpixel stays vendored — publishing to GH Packages is deferred. PAT consolidation is optional polish.

Key Changes

Already shipped to main (this cycle's investigation):

  • 97f27d4 — vendored the qpixel tarball into design/vendor/, repointed package.json to file:./vendor/qpoint-io-qpixel-0.1.0.tgz (fixes the ENOENT that broke every build).
  • 6f41e1d, 5e31670 — fixed deploy.yml npm auth (the .npmrc reads ${GITHUB_TOKEN}; it was being fed the wrong variable). Now uses a read:packages PAT.

Pending (the remaining fix):

  • Add CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID repo secrets (admin action — no code change). Moot after 2026-09-02.

Git-connected Pages (2026-09-02, outside this cycle): Tyler replaced the direct-upload qpoint-design project with a Git-connected design project (design-f39.pages.dev), fixing deploy.yml's auth in PR #14 and then deleting it in PR #15. Only pr.yml (install/build/generate check) remains. Pushes deployed green until 2026-09-21 (b94ce25). Known limit: Cloudflare clones only design, so the deployed cycle atlas lacks the sister repos.

Broken again (2026-09-25): both pushes failed on Cloudflare (f632245, 8fa2225). pr.yml fails npm ci with a 403 read_package on @qpoint-io/qmap@0.1.0 (token GH_NPM_READ_JON_PAT; rerun identical). The lockfile had been cached, so earlier runs never re-downloaded qmap; the layer 0.9.21 lockfile bump changed the cache key and exposed the missing access. The June Scope's "Out" rationale came true: the Git integration moved the auth problem into Cloudflare's builder. Mark's direction: make the build a manual local step (local GITHUB_TOKEN can read qmap, and a local build sees the sibling repos, so the atlas deploys complete). Options A (commit built output) / B (local wrangler pages deploy, the lean) and the decision queue → PICKUP.md. Parked the same day.

The deploy branch (2026-09-27):

  • 54290ab: npm run deploy runs scripts/deploy.sh. It builds committed HEAD in a sibling worktree, ../.design-deploy, and runs npm ci only when the lockfile changes. It then runs nuxt generate, writes /_deploy.json (design SHA, layer version, sibling SHAs), and force-pushes the output as one parentless commit to deploy
  • cb4dade: fixed a committed-only syntax error in component-registry.ts (NavVerticalSubmenu spliced mid-entry by c98's f3224f6), found by the first clean-worktree build
  • Cloudflare Pages design (Mark, in the dashboard): production branch deploy, no build command, output /, preview deployments and PR build comments off. Steps are in cloudflare-switch.md
  • ba55843, 675be1a: CLAUDE.md Deploy section plus the deploy npm script line, and this record
  • The cloudflare@cloudflare Claude Code plugin (skills plus the Cloudflare API MCP) was installed at user scope. Its OAuth didn't complete in the desktop session, so it went unused
  • Same day: all outstanding design work was committed in 10 thread-sized commits (83e904f…3fe2cf5: c93, c95, c97, c92 threads, registries) and shipped with npm run deploy

Outcome

Deploys are a local step. npm run deploy builds on Mark's laptop, and Cloudflare serves the result from the deploy branch with no build of its own. The site went live on 2026-09-27 (e6f79b9, design ba55843), the first update since the 09-21 freeze. The objective was "push to main reliably deploys". It was reframed on 2026-09-25 into "publishing is a deliberate local step that reliably deploys". Four deploys in the following three days prove it: Claude sessions for c49, c99 and c101 each ran npm run deploy and went live (the c99 report on 09-28, the c101 brief page d1bb068), with no Cloudflare credentials on any machine.

What the route settled:

  • No CI credentials. Cloudflare never installs packages, so the qmap 403 and the Cloudflare-secrets problem that sank June's attempt both drop out. The only credential is Mark's GITHUB_TOKEN, which already reads every @qpoint-io package
  • A complete atlas. The worktree sits beside the sister repos, so /cycles lists qmap, bob-wire, market-analyst, qflow and qpixel. Before this, only design's own cycles were live
  • Committed work only, and the dev server stays safe. Other sessions' uncommitted files stay out, and a live :3000 server is never clobbered
  • Provenance. /_deploy.json records exactly which commits are live
  • Small git footprint. The deploy branch holds one commit that is replaced on every deploy, and main never carries built files. Rollback is Cloudflare's per-deployment history

Learned:

  • A clean-worktree build is a free check on commits. It surfaces breakage that only exists in the committed tree, which a working-tree dev server hides. The first build caught a partial-hunk splice. Expect this whenever sessions commit with temp indexes
  • Every earlier fix moved the auth problem to another builder, as June's Scope predicted. Removing the remote builder removed the problem
  • The agent-plugin route to Cloudflare's API is available, but its OAuth didn't complete in the desktop app. The two dashboard settings were quicker by hand

Follow-ons (not blocking):

  • pr.yml still fails on every push to main (the 403 on @qpoint-io/qmap). It protects nothing now. Drop its install and build steps, delete it, or give GH_NPM_READ_JON_PAT read access to the qmap package
  • 8 non-fatal prerender errors per build: 7 × Cannot read properties of undefined (reading 'labels'), plus /api/cycles/design/todos-2026-09-23 returning 404. Find the pages and fix them or exclude them
  • Optional: an org admin grants qmap package access anyway, which would bring pr.yml back as a real check

⏭️ Next action — June 2026 (superseded 2026-09-02)

Everything upstream is already fixed and verified. The only remaining blocker is two GitHub repo secrets. Whoever has Cloudflare admin needs to:

  1. Create a Cloudflare API token — dashboard → My Profile → API Tokens → Create Token → custom token, scope Account → Cloudflare Pages → Edit, on the account that owns the qpoint-design Pages project.
  2. Get the Account ID — dashboard → Workers & Pages → right sidebar Account ID.
  3. Add both as repo secrets on qpoint-io/design:
    gh secret set CLOUDFLARE_API_TOKEN  --repo qpoint-io/design
    gh secret set CLOUDFLARE_ACCOUNT_ID --repo qpoint-io/design
    
  4. Trigger + watch the deploy (re-run latest deploy.yml, or push a trivial commit). The "Deploy to Cloudflare Pages" step has never run before — watch it authenticate, target qpoint-design, upload, and print a URL. If it fails at that step: project-name typo, wrong account, or token missing Pages:Edit.

Full detail, fallback, and verification → execution-plan.md


Artifacts

Qpoint Brand Style Guide