SOPs
Cloudflare Pages — Deploy via GitHub Actions Only
The Rule
Section titled “The Rule”Every Cloudflare Pages project must deploy through a GitHub Actions workflow copied from ~/apps/cc/templates/cloudflare-pages-deploy.yml. Never leave a Pages project as direct-upload.
A direct-upload project has no Git integration. Pushes to GitHub are silently ignored. No error, no email, no webhook failure, nothing. The failure only surfaces when someone notices content is missing, days later.
Why Direct-Upload Fails Silently
Section titled “Why Direct-Upload Fails Silently”Cloudflare Pages projects created by wrangler pages project create or by the API default to direct-upload mode. Direct-upload projects:
- Have no link to a GitHub repo
- Do not receive webhooks from GitHub
- Will happily ignore every push to
mainforever - Show
source: unknownin the Pages dashboard - Emit no error on
git push— the push succeeds, and nothing downstream runs
The Cloudflare dashboard’s “Connect to Git” flow requires a click-through OAuth consent that an agent cannot complete. So agent-created Pages projects default to the broken configuration unless the workflow is added explicitly.
GitHub Actions is the fix. It is fully automatable, the workflow file is versioned in the repo, every run shows up in the Actions tab, and the deploy status is visible on the commit. Same end result as dashboard Git integration, but no hidden state and no OAuth click.
The Precedent — tms-internal Builds 9 and 10, 2026-04-15
Section titled “The Precedent — tms-internal Builds 9 and 10, 2026-04-15”On 2026-04-15, the tms-internal repo had Builds 9 and 10 stuck in GitHub for more than 19 hours.
- Build 8: Last successful deploy, 2026-04-14.
- Build 9: Agency Model page. Pushed to GitHub. Never deployed.
- Build 10: Shortcuts page. Pushed to GitHub. Never deployed.
- Root cause: The tms-internal Pages project was created as direct-upload. The GitHub webhook was never wired. Every push was silently ignored.
- Discovery: Noticed the following afternoon when the content still was not live.
- Fix: Added
.github/workflows/deploy.ymlfrom the template. Added the two GitHub secrets. Pushed. Every commit since has auto-deployed in 90 to 120 seconds.
This SOP exists to make sure that never happens on any other repo.
Setup Checklist — New Cloudflare Pages Project
Section titled “Setup Checklist — New Cloudflare Pages Project”Follow in order. Do not skip steps.
- Create the Pages project. Either via the dashboard or
wrangler pages project create [name]. Name it after the repo for sanity. - Copy the workflow template into the new repo:
Terminal window mkdir -p .github/workflowscp ~/apps/cc/templates/cloudflare-pages-deploy.yml .github/workflows/deploy.yml - Edit the workflow. Replace
PROJECT_NAME_HEREwith the actual Pages project name. Confirm the build output directory (dist/for Astro,.next/for Next.js, etc.) matches your framework. - Add the two GitHub repo secrets via
gh secret setor the repo Settings UI:CLOUDFLARE_API_TOKEN— useCF_PAGES_DEPLOY_TOKENfromshared-secrets.env. It is scopedPages:Read+Pages:Write. Do not use the generalCF_API_TOKEN— that one is DNS-only and will fail with a 403 on Pages deploys.CLOUDFLARE_ACCOUNT_ID— also fromshared-secrets.env.
- Commit and push. The first push triggers the first Actions run. Watch it in the Actions tab.
- Verify in the Pages dashboard. The new deployment should appear within a minute. Source should say
CI/CDor similar, notdirect upload. - Verify the live URL.
curl -I https://[project].pages.devshould return 200. - Add the custom domain in Pages → Custom domains. DNS is automatic if the zone is on Cloudflare.
- Smoke test every subsequent push. First few commits, check the Actions tab to confirm the workflow ran. After that, trust it.
Required Secrets — Quick Reference
Section titled “Required Secrets — Quick Reference”| GitHub secret name | Source | Scope |
|---|---|---|
CLOUDFLARE_API_TOKEN | CF_PAGES_DEPLOY_TOKEN in shared-secrets | Pages:Read + Pages:Write |
CLOUDFLARE_ACCOUNT_ID | CLOUDFLARE_ACCOUNT_ID in shared-secrets | N/A |
Secrets live in a gitignored file inside the cc repo, kept in sync across every machine via scp. Use python3 ~/apps/cc/secrets/secrets_cli.py get CF_PAGES_DEPLOY_TOKEN to pull a value — never read the file directly, the CLI handles fallback paths.
Template Reference
Section titled “Template Reference”The canonical workflow template lives at ~/apps/cc/templates/cloudflare-pages-deploy.yml. It includes inline setup comments and a ready-to-use checkout → node setup → npm ci → npm run build → wrangler pages deploy pipeline.
The tms-internal repo’s own deploy.yml is the reference implementation in production. Copy from the template, not from another repo, so you always get the latest version of the boilerplate.
Recovery — Existing Direct-Upload Project
Section titled “Recovery — Existing Direct-Upload Project”If you discover an existing Pages project that is stuck in direct-upload mode (pushes happening, deploys not landing), the recovery is identical to the new setup above. You do not need to delete or recreate the Pages project — adding the workflow and secrets is enough. Cloudflare does not care that the project was originally created as direct-upload; once the workflow starts pushing builds via wrangler pages deploy, new deployments just start landing.
One manual wrangler pages deploy dist --project-name [name] run is a fast way to confirm the secrets and project name are correct before trusting the Actions workflow.