| name | cloudflare-deploy-rules |
| description | Cloudflare deployment hard constraints. MUST authenticate via scoped API Token in CLOUDFLARE_API_TOKEN env var (loaded from git-ignored .env), MUST bypass HTTP proxy on every Cloudflare API call (curl --noproxy '*', wrangler with NO_PROXY="*" no_proxy="*"), MUST extract account ID from /zones?name=<domain> when token lacks Account:Read and persist as CLOUDFLARE_ACCOUNT_ID. NEVER run `wrangler login` (OAuth breaks behind proxies/VPN), NEVER let a Cloudflare request traverse the local ClashX/HTTP proxy, NEVER commit credentials. Activates on any wrangler command, api.cloudflare.com call, or CLOUDFLARE_* env edit. MUST read SKILL.md BEFORE running wrangler commands, calling Cloudflare API, or editing CLOUDFLARE_* env vars. |
| license | MIT |
| metadata | {"author":"motiful","version":"1.0"} |
Cloudflare Deploy Rules — Hard Constraints
Rule-skill for Cloudflare deployment operations. No companion capability skill — this is a standalone environment-triggered rule-skill.
Treat every MUST/NEVER below as equivalent authority to platform-native rule files. The meta-rule protocol (rules-as-skills) raises load priority to MUST-level automatically on any -rules suffix skill.
Immutable by agent self-modification.
Scope
Activates on any of the following actions:
- Any invocation of the
wrangler CLI (wrangler deploy, wrangler publish, wrangler secret, wrangler kv, wrangler r2, wrangler pages, etc.)
- Any HTTP call to
api.cloudflare.com (curl, fetch, Axios, Python requests, Cloudflare SDK clients, etc.)
- Any edit to
CLOUDFLARE_* environment variables (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_ZONE_ID, etc.) in .env, .envrc, shell rc files, or CI secret stores
- Any Cloudflare deployment script or Makefile/npm-script target that wraps the above
Does not apply to: reading Cloudflare public documentation, reviewing non-Cloudflare Terraform/IaC, or inspecting Cloudflare-origin responses in an application runtime (that is application traffic, not deploy traffic).
Auth Constraints
- NEVER run
wrangler login. The OAuth flow requires a localhost callback that is broken by local proxies/VPNs (ClashX, Surge, corporate proxies) and is not CI-compatible.
- MUST authenticate via a scoped Cloudflare API Token exposed in the
CLOUDFLARE_API_TOKEN environment variable. Wrangler and the Cloudflare API both honor this variable natively.
- MUST store
CLOUDFLARE_API_TOKEN (and any other CLOUDFLARE_* secret) in a git-ignored .env file and load it via source .env (or equivalent) before invoking wrangler or any API client.
- NEVER commit a file containing a real
CLOUDFLARE_API_TOKEN value. .env MUST be listed in .gitignore (or the repo's secret-handling convention) before the token is written to disk.
- MUST create API Tokens with least-privilege scopes for the target resource (Workers, Pages, R2, KV, Zone DNS, etc.). Global API Keys are disallowed.
Rationale: API Tokens are permanent, scoped, and CI-compatible; OAuth sessions are user-bound and fail on headless/proxied environments. Credential discipline (.env + .gitignore) prevents the single most common Cloudflare incident class: leaked tokens in git history.
Proxy Constraints
- MUST bypass the local HTTP proxy on every Cloudflare API call. ClashX, Surge, and similar local proxies either time out (
ETIMEDOUT) against Cloudflare endpoints or corrupt auth headers in flight, producing opaque 401/403 errors that look like credential bugs but are proxy bugs.
- MUST invoke
curl against api.cloudflare.com with --noproxy '*' so the request ignores HTTP_PROXY/HTTPS_PROXY env vars for that call.
- MUST invoke
wrangler with NO_PROXY="*" no_proxy="*" prefixed (lowercase and uppercase both required — Node's undici honors one, libcurl honors the other).
- MUST apply the bypass to any SDK client too (e.g., set
NO_PROXY / no_proxy or pass the agent's noProxy option). If the SDK does not support proxy bypass, prefer direct curl --noproxy '*' invocations.
- NEVER assume a Cloudflare API failure is a credential problem until the proxy bypass has been verified. The failure mode is observationally identical — intermittent
ETIMEDOUT, unexpected 401/403, mangled X-Auth headers — but the fix is different.
Rationale: Local traffic-routing proxies classify api.cloudflare.com as "internet-bound" and try to tunnel it through upstream rules that strip or mutate headers. Bypassing the proxy at request level is the only reliable workaround on a developer machine running ClashX/Surge.
Account ID Constraints
-
MUST obtain CLOUDFLARE_ACCOUNT_ID before any operation that requires it (Workers deploy, R2 bucket ops, KV namespace ops, most API POST/PUT endpoints).
-
MUST extract account ID from zone info when the API Token lacks Account:Read scope:
curl --noproxy '*' \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=<domain>" \
| jq -r '.result[0].account.id'
-
MUST persist the result in .env as CLOUDFLARE_ACCOUNT_ID=<value> once discovered, so subsequent sessions do not re-derive it.
-
NEVER hardcode the account ID in committed source files. Treat it as a low-sensitivity secret: not a credential, but environment-specific configuration that belongs in .env.
Rationale: A least-privilege token scoped to a single zone often cannot list accounts directly. Deriving the account ID from zone metadata is the officially documented workaround and avoids escalating the token's scopes.
Verification Procedure (for Agents)
Before running any wrangler command, Cloudflare API call, or CLOUDFLARE_* env edit:
def verify_cloudflare_environment(action, env, files) -> None:
assert "CLOUDFLARE_API_TOKEN" in env, "CLOUDFLARE_API_TOKEN missing from environment"
assert env["CLOUDFLARE_API_TOKEN"].startswith("") and len(env["CLOUDFLARE_API_TOKEN"]) >= 40, \
"token looks truncated or malformed"
assert action != "wrangler login", "wrangler OAuth is forbidden — use API Token"
assert ".env" in read_gitignore(files), ".env must be git-ignored before writing secrets"
assert not token_committed_in_git_history(files), "CLOUDFLARE_API_TOKEN appears in git history — rotate immediately"
if action.starts_with("curl") and "api.cloudflare.com" in action:
assert "--noproxy '*'" in action, "curl against api.cloudflare.com requires --noproxy '*'"
if action.starts_with("wrangler"):
assert env.get("NO_PROXY") == "*" and env.get("no_proxy") == "*", \
"wrangler requires NO_PROXY=* and no_proxy=* on this machine"
if action_requires_account_id(action):
if "CLOUDFLARE_ACCOUNT_ID" not in env:
advise("derive account ID: GET /zones?name=<domain> → .result[0].account.id, persist to .env")
halt()
Fail any check → do not execute the action. Fix the underlying violation first (add .env to .gitignore, source the token, prefix the proxy-bypass env vars, derive account ID) and re-verify.
Migration from ~/.claude/rules/cloudflare-deployment.md
This rule-skill replaces the legacy global rule file at ~/.claude/rules/cloudflare-deployment.md (19 lines, same three sections). Content is lifted verbatim where possible and expanded with:
- A
Scope declaration (environment trigger surface)
- Per-section rationale blocks
- The
verify_cloudflare_environment() EP
- Cross-platform coverage via the
-rules suffix + rules-as-skills meta-rule protocol (Claude Code, Codex, Cursor, Windsurf, OpenClaw)
The legacy global rule file SHOULD be deleted after this rule-skill is installed, symlinked, and pushed — the meta-rule protocol gives this skill MUST-level priority uniformly across platforms, while the legacy file only covered Claude Code.