# Cloudflare Deploy Rules

> 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.

- Skill: `motiful/cloudflare-deploy-rules` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add motiful/cloudflare-deploy-rules`
- Raw SKILL.md: https://api.skillmd.com/api/skills/motiful/cloudflare-deploy-rules/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: motiful (https://skillmd.com/u/motiful)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/motiful/cloudflare-deploy-rules

---


# 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](https://github.com/motiful/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:

  ```bash
  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:

```python
def verify_cloudflare_environment(action, env, files) -> None:
    # Auth
    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"

    # Proxy
    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"

    # Account ID (only when the action needs it)
    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`](https://github.com/motiful/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.

