Fix the Cloudflare Pages Deploy
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:
| # | Misconfiguration | Introduced | Status |
|---|---|---|---|
| 1 | npm auth: .npmrc reads ${GITHUB_TOKEN} but the Action fed the auto repo token (can't read sibling @qpoint-io/* packages) → 401 | 2026-04-13 | ✅ Fixed (6f41e1d, 5e31670) |
| 2 | qpixel dep file:../qpixel/…tgz (sibling path absent on CI) → ENOENT | 2026-04-16 | ✅ Fixed — vendored to design/vendor/ (97f27d4) |
| 3 | Cloudflare creds (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID) never set, never tested | never | ⛔ 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@v3for a directnpx wrangler@4 pages deployCLI 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 intodesign/vendor/, repointedpackage.jsontofile:./vendor/qpoint-io-qpixel-0.1.0.tgz(fixes the ENOENT that broke every build).6f41e1d,5e31670— fixeddeploy.ymlnpm auth (the.npmrcreads${GITHUB_TOKEN}; it was being fed the wrong variable). Now uses aread:packagesPAT.
Pending (the remaining fix):
AddMoot after 2026-09-02.CLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_IDrepo secrets (admin action — no code change).
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 deployrunsscripts/deploy.sh. It builds committed HEAD in a sibling worktree,../.design-deploy, and runsnpm cionly when the lockfile changes. It then runsnuxt generate, writes/_deploy.json(design SHA, layer version, sibling SHAs), and force-pushes the output as one parentless commit todeploycb4dade: fixed a committed-only syntax error incomponent-registry.ts(NavVerticalSubmenu spliced mid-entry by c98'sf3224f6), found by the first clean-worktree build- Cloudflare Pages
design(Mark, in the dashboard): production branchdeploy, no build command, output/, preview deployments and PR build comments off. Steps are in cloudflare-switch.md ba55843,675be1a: CLAUDE.md Deploy section plus thedeploynpm script line, and this record- The
cloudflare@cloudflareClaude 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 withnpm 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-iopackage - A complete atlas. The worktree sits beside the sister repos, so
/cycleslists 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
:3000server is never clobbered - Provenance.
/_deploy.jsonrecords exactly which commits are live - Small git footprint. The
deploybranch holds one commit that is replaced on every deploy, andmainnever 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.ymlstill fails on every push tomain(the 403 on@qpoint-io/qmap). It protects nothing now. Drop its install and build steps, delete it, or giveGH_NPM_READ_JON_PATread 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-23returning 404. Find the pages and fix them or exclude them - Optional: an org admin grants qmap package access anyway, which would bring
pr.ymlback 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:
- 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-designPages project. - Get the Account ID — dashboard → Workers & Pages → right sidebar Account ID.
- 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 - 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, targetqpoint-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
- PICKUP.md — parked-state re-entry note
- PICKUP.md
- cloudflare-switch.md