cloudflare-workers-builds-keyless-deploy
Skill0 starsChanged 5 days agoMITView on GitHub
Deploy Cloudflare Workers from GitHub with no Cloudflare credential stored in GitHub (no CLOUDFLARE_API_TOKEN in Actions secrets) using Workers Builds, Cloudflare's git-connected CI/CD. Use when setting up or migrating a Workers project so an autonomous agent pipeline never holds a Cloudflare secret, when asked "can we deploy without a CF API token in CI", or when driving the dashboard connect ceremony by hand or with a browser agent. Covers the dashboard traps — a My Profile token missing from the build-token picker, the picker defaulting to another project's token, Root directory hidden under Advanced settings as "Path", the Create-an-app wizard refusing an existing Worker's name, the first build posting no check-run, preview builds sharing the production D1, Workers Builds not waiting for GitHub CI (gate with a ruleset), and build watch paths for docs-only commits.
- Installs packages
In this repository23 other files it gives its agents.
- skills/vercel-react-best-practices/AGENTS.mdAGENTS.md
- agent-status-hubSkill
- claude-code-docker-sandboxSkill
- cloudflare-api-token-permissionsSkill
- cloudflare-cron-to-discordSkill
- cloudflare-d1-drizzle-migrationSkill
- cloudflare-d1-keyless-host-backupSkill
- cloudflare-d1-weekly-backup-via-prSkill
- cloudflare-mcp-claude-toolingSkill
- cloudflare-r2-private-image-uploadSkill
- cloudflare-workers-bot-scan-defenseSkill
- cloudflare-workers-deploy-skeletonSkill
- cloudflare-workers-e2e-playwrightSkill
- cloudflare-workers-host-synthetic-checkSkill
- cloudflare-workers-passkey-authSkill
- cloudflare-workers-pat-bearer-authSkill
- cloudflare-workers-space-membership-inviteSkill
- cloudflare-workflows-for-long-tasksSkill
- playwright-e2e-in-docker-sandboxSkill
- sandboxed-agent-github-token-via-1passwordSkill
- sandboxed-agent-git-relaySkill
- snr-writingSkill
- vercel-react-best-practicesSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 21
NOT for: Pages projects (different product), pipelines that must deploy from non-GitHub/GitLab CI (fall back to a minimal-permission custom …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Cloudflare Workers Builds: Keyless Deploy
Replace "GitHub Actions runs wrangler deploy with a CLOUDFLARE_API_TOKEN secret" with Workers Builds: Cloudflare pulls the repo via its GitHub App and builds/deploys on Cloudflare's side. The deploy credential (a build token) lives inside Cloudflare and never exists in GitHub, the repo, or any dev sandbox. GitHub Actions keeps only test/lint — Actions Secrets become empty.
Why not OIDC? As of 2026-08, the Cloudflare API has no OIDC / workload identity federation (wrangler-action supports only apiToken; the OIDC feature request remains an open GitHub Discussion — workers-sdk#11434). Workers Builds is the only mechanism where GitHub holds zero Cloudflare credentials. Verified against live docs 2026-08-08; the dashboard ceremony below was re-run and corrected on kokemusu 2026-08-23 (see Verified on kokemusu).
When to use this skill
- New or existing Workers project deployed from a GitHub repo, especially with an autonomous agent in the loop (no long-lived secret should be reachable from CI or a sandbox)
- Migrating off a
deploy.yml+CLOUDFLARE_API_TOKENsetup - NOT for: Pages projects (different product), pipelines that must deploy from non-GitHub/GitLab CI (fall back to a minimal-permission custom token — see
cloudflare-api-token-permissions)
Architecture and the one invariant
Workers Builds triggers on push and does NOT wait for GitHub CI results. So the gate moves to merge time:
Invariant: code on
mainis always CI-green — enforced by a branch ruleset (PR required + required status check + no bypass actors). Workers Builds then only ever builds green code.
PR branch push → GitHub Actions ci (typecheck/test) [GitHub side, no secrets]
merge to main (ruleset: PR + check "ci" required) [human or auto-merge]
└→ Workers Builds: install → build → D1 migrate → wrangler deploy [Cloudflare side]See references/ruleset.md for the exact gh api ruleset payload (pins the required check to the GitHub Actions app via integration_id: 15368, bypass_actors: [] so even the repo owner cannot push main) and references/ci-yml.md for the secrets-free CI workflow.
One-time human ceremony (secret-zero)
Once per project, by a human or a browser agent under human supervision (it creates a credential). Screen-by-screen labels (Japanese UI, English equivalents), direct URLs and browser-agent tips are in references/dashboard-walkthrough.md — read it before driving the dashboard.
Pre-flight, from the terminal, before opening the dashboard (each of these stopped the kokemusu run):
git fetch origin && git ls-tree --name-only origin/main apps/web/wrangler.jsonc # must print the path: the production branch must ALREADY contain the root directory
gh pr list --state open # the skeleton PR must be merged, not open
grep -n '"name"\|database_id' apps/web/wrangler.jsonc; wrangler d1 list # name = the Worker name you will type; database_id = a real UUID that exists
wrangler whoami # the account the dashboard is logged into (the browser may run on another machine — only the account matters)
wrangler deployments list # DECIDES THE ROUTE: "This Worker does not exist" [10007] → steps A; deployments listed → steps BTwo routes, and that last command decides which. A Worker that does not exist yet is created by the Create an app wizard — steps A. A Worker that already exists — typically one you have been deploying with wrangler deploy from GitHub Actions, i.e. exactly the migration this skill is for — cannot go through that wizard: it validates the project name against existing Workers and refuses with "A project with this name already exists. Choose a different name." (この名前のプロジェクトはすでに存在します。別の名前を選択してください). Connect it from the Worker's own settings instead — steps B. Do not rename the Worker to get past the wizard: the name is its workers.dev hostname, and for a WebAuthn app that invalidates every registered passkey. (The wizard rejects at validation, before anything is created — no orphan token, no orphan Worker.)
Steps A — a Worker that does not exist yet (dashboard as of 2026-08-23):
- GitHub App scope. Workers & Pages → Create → Continue with GitHub. If the Cloudflare Workers and Pages GitHub App is already installed on the GitHub account (from an earlier project), no authorization screen appears — the repository list shows straight away with whatever scope that installation has. To keep Only select repositories, manage it on GitHub (Settings → Applications → Cloudflare Workers and Pages → Repository access) before connecting; narrowing an existing All repositories installation touches every project built from that account, so list them first.
- Setup dialog. Project name = exactly
namefrom wrangler.jsonc; build + deploy commands from the table below; untick "Builds for non-production branches" (it is in the dialog, ticked by default — no need to wait for Settings). Then open the "Advanced settings" accordion at the bottom: it hides three things — Root directory (labelled Path, default/), the API token picker, and build variables. In the picker choose Create new token and name it<worker> Workers Builds. Do not leave the default: the picker pre-selects the build token of whatever project you connected last (kokemusu's default wasnyalog Workers Builds), which silently couples the two projects' deploy credentials. - Deploy. Click Deploy: the Worker is created (two placeholder
Uploaddeployments appear at once) and the first build starts as a manual build of the production branch HEAD — roughly 2 minutes (init ≈ 50 s, clone, install, build, migrate + deploy). A Workers Paid upsell modal appears — dismiss it. The build page's Build settings panel shows exactly what was saved (commands, root directory, token name) — confirm it there. - Settings → Builds afterwards: confirm token and branch control, add build watch paths excludes (
docs/*,*.md: type, Enter, then Save on the toast). Nothing else.
Steps B — a Worker that already exists (matatabetai 2026-09-02). Workers & Pages → the Worker → 設定 / Settings → right-hand nav ビルド / Builds → Git リポジトリ card → 接続 / Connect. Same values as the table below, but the dialog differs from the wizard:
- No project name field — the Worker is already named, which is the point of this route. Repository and 本番ブランチ / Production branch (
main) come pre-filled; the build command loads a beat later (読み込み中...→pnpm run build). - The branch tick-box is labelled プレビュービルドを有効化 / Enable preview builds (not Builds for non-production branches) and is ticked — untick it. Confirm by eye: the 非本番ブランチのデプロイ コマンド field disappears when it is really off.
- 詳細設定 / Advanced settings holds the same Path and API-token picker, plus a ビルド キャッシュ / Build cache toggle (off by default — leave it).
- In the picker, Create new token sits at the top of the list here (bottom in the wizard), and choosing it pre-fills the token name as
Workers Builds - <YYYY-MM-DD HH:MM>— overwrite it with<worker> Workers Builds. The pre-selected default is still another project's token. - Connect starts no build. The page returns with "Git リポジトリにコミットをプッシュして最初のビルドを開始できるようになりました" (push a commit to start the first build). So on this route the first build is push-triggered and does post a
Workers Builds: <worker>check-run — the "first build is manual, no check-run" caveat applies to steps A only. Make that first push the commit that deletesdeploy.yml: the old workflow is gone from the ref being pushed, so Actions does not deploy and the two pipelines never race. - Then the same watch-path excludes as A4, on the page you are already on.
Dropped step — do not pre-create a custom token in My Profile. The 0.2.0 recipe said to create a Workers Scripts + D1 + Account Settings + User Details + Memberships custom token first and "select it from the list". On 2026-08-23 that token did not appear in the picker at all (searched by name → "No labels found"); the picker listed only the dash-generated <project> Workers Builds tokens of other projects plus Create new token — even though the docs say you may "select one that you already own". If you already made one, delete it in My Profile → API Tokens: it is an unused token with Edit rights.
Settings that matter
| Setting | Value | Trap if wrong |
| Worker/project name (steps A only) | exactly name from wrangler.jsonc | name mismatch → deploy creates a second Worker. If the name is taken, you are on the wrong route — use steps B |
| Root directory (Advanced settings!, labelled Path in the setup dialog) | the package dir containing wrangler.jsonc, e.g. apps/web | commands run at repo root; build + deploy both fail. Also fails if the production branch doesn't contain that directory yet (unmerged skeleton PR) — a branch problem that looks like a settings problem |
| Build command | pnpm install --frozen-lockfile && pnpm run build | explicit install guards monorepo lockfile auto-detection (lockfile lives at repo root, root directory doesn't); pnpm finds the workspace root upward automatically (log shows Scope: all N workspace projects) |
| Deploy command | pnpm exec wrangler d1 migrations apply <DB_NAME> --remote && pnpm exec wrangler deploy | migrations must precede deploy; pnpm exec uses the repo-pinned wrangler |
| API token (Advanced settings!) | Create new token in the picker, named <worker> Workers Builds | The docs (checked 2026-08-23) still describe the generated token as Account Settings read, Workers Scripts / KV / R2 edit, Workers Routes edit, User Details / Memberships read — no D1. The dashboard notice on the same day listed a wider set including D1 Storage (edit) (plus Vectorize, Queues, Pipelines, Containers, Cloudchamber, Connectivity Directory, AI Search), and d1 migrations apply --remote succeeded on the first build. If a build fails at the migrate step with Authentication error [code: 10000], open My Profile → API Tokens and add D1 Edit to that token in place (cloudflare-api-token-permissions) — the error names auth but not the missing permission. Never accept the picker's pre-selected default (another project's token). Old wrangler (<3.83.0) failed with no output at all (workers-sdk #5077, closed 2026-04) |
| Branch control | production branch = main; non-production branch builds OFF (tick-box is in the setup dialog and in Settings → Builds) | ⚠️ preview versions share the production D1 binding (preview_database_id applies only to wrangler dev, not uploaded versions) — PR previews would hit prod data, and a preview running migrations would migrate prod |
| Build watch paths (Settings → Builds) | default * = build everything; to skip docs-only deploys, exclude docs/* and *.md (keep include *) | excludes are evaluated first and match only docs/markdown, so a code change is never skipped and a mixed commit still builds; safe because the required ci check is independent of Workers Builds (still runs, still gates merge). Do NOT skip CI via paths-ignore — the required check goes pending and the PR sticks. Always builds on 0-change / 3000+ files / 20+ commits. A skipped commit gets NO Workers Builds: check-run at all — not a check-run with conclusion skipped — so it is indistinguishable from a missed trigger unless you look at the commit's paths (see failure modes). Excludes match nested paths: docs/* + *.md skipped a commit touching CLAUDE.md, docs/log.md and docs/plans/host-setup.md (matatabetai 2026-09-02). Dashboard-only (not in wrangler.jsonc); the exclude box's grey node_modules/**, .git/ is placeholder text, not a value |
Plan limits (2026-06): Free = 3,000 build min/month, 1 concurrent build, 20 min timeout — plenty for a solo project.
Verification
# Cloudflare reports build status as a GitHub check run on the commit:
gh api repos/<owner>/<repo>/commits/<sha>/check-runs \
--jq '.check_runs[] | {name, status, conclusion}'
# → {"name":"Workers Builds: <worker>","conclusion":"success"} + your "ci" check
# If `gh api` is denied by the project's .claude/settings.json, a PUBLIC repo answers unauthenticated:
curl -s -H "Accept: application/vnd.github+json" \
https://api.github.com/repos/<owner>/<repo>/commits/<sha>/check-runs
wrangler deployments list # new deployment after merge
wrangler d1 migrations list <DB> --remote # "No migrations to apply!" = applied
curl https://<worker>.<subdomain>.workers.dev/healthAfter steps A the production HEAD carries only the ci check-run. That first build was a manual build and posts no Workers Builds: <worker> check — expected, and not the missed-trigger case below. (Steps B starts no build at all, so its first build is already push-triggered and does post the check-run.) The push → build path is proven only by the next commit to main (under the ruleset: a PR). Until then the evidence is the dashboard Builds tab plus wrangler deployments list, where the Workers Builds deploy shows Source: Unknown (deployment) and the two Upload entries created at the same minute are the Worker-creation placeholders.
Note: workers.dev URLs are always <worker-name>.<account-subdomain>.workers.dev — a bare <name>.workers.dev does not exist. If the account subdomain matters (OAuth redirect URIs, WebAuthn RP_ID), decide/rename it before registering those, since renaming changes every Worker URL in the account.
Build status lives in the Workers Builds: <worker> check-run above — that plus wrangler deployments list is how you read pass/fail/queued from the CLI. The Workers Builds REST API (/accounts/<id>/builds/*, documented at /workers/ci-cd/builds/api-reference/) needs a user token carrying Workers Builds Configuration: Edit — so a wrangler login OAuth token gets 10000 Authentication error (a real route the token lacks scope for, vs 7003 for a bogus path), and wrangler has no builds subcommand. For queued/running/failed detail beyond the check-run, use the dashboard Builds tab — the Version history tab lists only successful deploys, never failures/skips.
Failure modes seen in the wild
- The wizard rejects the project name: "A project with this name already exists" → the Worker already exists (you are migrating off
wrangler deploy/Actions). Not a name you should change — leave the wizard and use steps B (the Worker's Settings → Builds → Git リポジトリ → Connect). - "My custom token is not in the API token picker" → expected as of 2026-08-23 (see Dropped step). Use Create new token; do not loop back to My Profile.
- Build fails immediately, weird path errors → Root directory not set (it's Path inside Advanced settings) — or the production branch does not contain that directory yet (skeleton PR unmerged). Check
git ls-tree origin/main <root>/wrangler.jsoncbefore touching settings. - Build green until migration step,
Authentication error [code: 10000]→ the selected build token lacks D1 Edit (another project's older token, or a generated token from before D1 was included). Add D1 Edit to the token in place, or switch to Create new token in Settings → Builds, then Retry once. - First build green but no
Workers Builds:check-run on GitHub → the first build is manual; prove the trigger with a real push tomaininstead of re-running it. - Nothing builds on push → (a) push to a non-production branch with branch builds off (intended); (b) watch-paths excluded everything (see Settings); or (c) a transient missed trigger — even with a healthy connection, a production-branch merge, and watch-paths=
*, Cloudflare occasionally creates no build at all for a commit. Diagnose with the commit's check-runs: a built commit carries aWorkers Builds: <worker>check-run (appcloudflare-workers-and-pages); if onlyciis present the build was never triggered — distinct from a failed check-run (built then failed). Rule out (b) before concluding (c): an excluded commit produces exactly the same signature (no check-run whatsoever), so first check what the commit touched —git show --stat <sha>— against the exclude list. Only a commit touching at least one non-excluded path and still carrying no check-run is a missed trigger.gh api repos/<owner>/<repo>/commits/<sha>/check-runs --jq '.check_runs[].name'. Re-trigger by pushing a new commit tomain— retrying the latest build in the dashboard rebuilds that commit, not the missed one. (Don't readcommits/<sha>/statustotal_count:0as "no signal" — Workers Builds & Actions both report via the Checks API, not legacy statuses.) - Deploy succeeded but old code serves → check
wrangler deployments list; the dashboard build log tells you which commit was built.
Verified on matatabetai (2026-09-02) — the existing-Worker route
Third application. Worker matatabetai (account subdomain shiraoka), pnpm monorepo, root directory packages/web, already deployed by GitHub Actions — the first time this skill met a Worker that existed.
- Pre-flight was all green by the 0.3.1 reading (
deployments listprinted 10 deployments, which 0.3.1 called "would be taken over") — and the wizard still refused at the project-name field. That sentence was wrong; it now selects the route. Nothing was created by the refusal: the API-token picker afterwards still listed only the other projects' tokens. - Steps B connected on the first try. The saved panel survived a reload: root directory
packages/web, production branchmain, preview builds off, watch-path excludesdocs/*+*.md, tokenmatatabetai Workers Builds. - The generated token's permission notice (expanded before connecting) again included D1 Storage (編集) and Workers R2 Storage (編集), plus KV, Vectorize, Queues, Pipelines, Containers, Cloudchamber, AI Search (edit), Connectivity Directory (read+bind), Workers routes on all zones, and user details + memberships (read). Both bindings this project needs were covered without touching My Profile.
- The picker default was again another project's token (
kokemusu Workers Builds) — third project, third time. - Watch-path excludes verified. The next merge after the deploy.yml one touched only
CLAUDE.md,docs/adr/002-*.md,docs/log.md,docs/plans/host-setup.md,docs/roadmap.md,docs/status.md. Two minutes later the commit still carried onlyciand/healthstill served the previous deploy — so the excludes matched every one of those nested paths, and a skip is silent: no check-run is created. - The push-triggered build is now verified end to end. Merging the
deploy.yml-deleting PR put bothci(success) andWorkers Builds: matatabetai(success) on the merge commit, and/healthanswered 200. So on route B the very first build is push-triggered, posts the check-run, and the old Actions deploy does not race it — the workflow is absent from the ref being pushed.
Verified on kokemusu (2026-08-23) — and what is still open
Second application of this skill (after mazuoboeru / nyalog), driven by a browser agent (Claude in Chrome on a Mac, CLI on Linux). Worker kokemusu, account subdomain shiraoka, pnpm monorepo with root directory apps/web.
- Build
#de3ceaea: init 53 s / clone 4 s / install 18 s / build 11 s / deploy 36 s. Log:Detected the following tools from environment: pnpm@9.15.0, nodejs@24.18.0,Scope: all 2 workspace projects. Migrate + deploy succeeded first try on the dash-generated token;/health→ 200,d1 migrations list --remote→No migrations to apply!. - The 0.2.0 "custom token first" step cost a full token-creation round and produced an orphan token (
workers-builds-kokemusu, 5 permissions, never selectable) — hence Dropped step above. - The picker's default was another project's token (
nyalog Workers Builds) with a yellow "missing permissions: email_routing_…" notice — that notice is noise unless the Worker uses Email Routing. - Pre-flight mattered:
maindid not yet containapps/web(three PRs open) — connecting at that point would have failed the first build at the path step for a non-settings reason. - UNVERIFIED: why a hand-made user token is absent from the picker (filtered by creator? by full permission set? — the docs say existing tokens are selectable). Re-check on the next project before restoring the old step.
- UNVERIFIED on kokemusu specifically (only its manual first build has run) — but the push → build →
Workers Builds: <worker>check-run path itself is confirmed on matatabetai 2026-09-02, so expect the same here. - UNVERIFIED: the English labels of the setup dialog (run under the Japanese dashboard); Path / Advanced settings are translations, Root directory is confirmed by the docs' settings table.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

