Skip to content

feat(claude-code): ship Cloudflare cf CLI with telemetry off - #204

Merged
gatezh merged 4 commits into
masterfrom
feat/cf-cli
Oct 8, 2026
Merged

gatezh merged 4 commits into
masterfrom
feat/cf-cli

Conversation

@gatezh

@gatezh gatezh commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

What

Ships Cloudflare's new cf CLI in both claude-code targets, with telemetry off, and recommends that consumer repos migrate to the typed cloudflare.config.ts.

Why

cf (open beta since 2026-09-28) covers the whole Cloudflare API (~2,900 commands, compared with ~280 in Wrangler), prints JSON and finds commands locally with cf cli search. Once the beta ends, Cloudflare will maintain Wrangler for only 18 more months. The Cloudflare MCP server stays: it also searches current Cloudflare docs, and cf doesn't yet cover everything (wrangler tail, setting a single secret).

Changes

  • Dockerfile: installs cf from npm, pinned in CF_VERSION. It sits in the shared stage after Chromium and before Claude Code, so Claude Code bumps don't reinstall it. Sets CF_SEND_TELEMETRY=false and WRANGLER_SEND_METRICS=false, and pre-creates a node-owned ~/.config/cloudflare.
  • Size trim: installs with npm --ignore-scripts, then deletes cf's bundled workerd runtime (133 MB). That brings cf from 225 MB to 92 MB. Skipping install scripts is what keeps it to one rm: workerd's postinstall would otherwise hard-link the binary a second time. It also means no third-party install code runs in the build.
  • Managed settings (managed-settings.json):
    • The claudeMd key tells every session to prefer cf, but not in a project with a Wrangler config and no cloudflare.config.ts. There, cf dev/build/deploy rewrite package.json, the lockfile and vite.config.ts without asking when there's no terminal.
    • permissions.ask covers Bash(cf * --force*) and Bash(cf * -f*). Without a terminal, cf aborts deletes unless they carry that flag, so Claude Code asks before every destructive Cloudflare call. Bypass mode skips prompts by design.
  • Shell completion (zsh): gh through oh-my-zsh's built-in gh plugin, which regenerates _gh on each shell start. cf through cf complete zsh >> ~/.zshrc at build time, as Cloudflare documents. The script asks cf for candidates, so it doesn't go stale across upgrades.
  • Renovate: cf joins the "devcontainer tools" group, so new releases wait 3 days before auto-merging.
  • Templates: both devcontainer.json files gain a myproject-cloudflare-config-* volume, plus the chown safety net, so cf auth login survives rebuilds. The sandbox firewall template allows api.cloudflare.com and dash.cloudflare.com.
  • CI verify (ci.yml and build-claude-code.yml): checks cf --version, cf cli search, the cf guidance in claudeMd, the ask rule, both telemetry variables, the owner of ~/.config/cloudflare, and that both completions are registered. build-claude-code.yml's verify commands had fallen behind ci.yml's: they were missing the agent-browser, browser-skill and agent-browser config checks. The two are now identical, so the post-merge build checks what the PR build checked.
  • Docs: the claude-code README gets a "Cloudflare CLI (cf)" section and "Recommended: migrate Workers projects to cloudflare.config.ts". It also recommends a least-privilege API token for agents in the sandbox, which shares the login, and for CI. The hugo-bun-node README now points to cf.

Notes

  • Trim trade-off: cf dev and --local commands need cf as a project dev dependency, which cf init and cf migrate add. The global cf then runs the project's copy, which has its own workerd. I checked this: in a project, cf d1 raw <id> --local works; in a bare directory it fails with a clear "workerd could not be found" error. API commands don't need workerd.
  • Images without cf: claude-bun can't run it: it has no Node, and cf can't load cloudflare.config.ts under Bun. ralphex-fe has Node 24, but it's a standalone ralphex runner and out of scope here. (Corrected after merge: this note originally said ralphex-fe had no Node too.)
  • Consumer action: add the volume and the two firewall domains to your own .devcontainer/ (the upstream-sync skill picks this up), then run cf auth login --no-browser once.
  • Firewall: a sign-in probe showed cf auth login (OAuth device flow) contacts only dash.cloudflare.com, and API calls go to api.cloudflare.com. Those IPs return 403 for other Cloudflare-hosted sites (workers.dev, chatgpt.com), so the allowlist doesn't open the rest of Cloudflare's network. End to end through the sandbox firewall (sandbox image, NET_ADMIN, the template script): example.com stays blocked, cf auth login --no-browser reaches the device-code step, and an API call with a dummy token gets a real 400 from api.cloudflare.com.
  • Why claudeMd, not a file: the Claude Code docs give a /etc/claude-code/CLAUDE.md file and the claudeMd key the same precedence. master now uses claudeMd for the browser guidance, so this PR follows suit.
  • Overlap with Cloudflare's wrangler skill: it already says to use cf when cloudflare.config.ts exists, but it loads only on demand. The standing claudeMd line is what Cloudflare's agent docs recommend.
  • Tested locally (arm64): actionlint, shellcheck and hadolint pass. Both targets build, and the exact ci.yml verify commands pass on each. Completion returns real candidates: cf complete -- dns rec gives records.

gatezh added 4 commits October 8, 2026 12:37
Install Cloudflare's cf CLI (open beta) in both targets, pinned and bumped by
Renovate with the same 3-day soak as the other devcontainer tools.

- Delete cf's bundled workerd runtime (hard-linked twice, 133 MB): cf dev and
  --local commands run the project's own cf copy. cf is 92 MB instead of 225 MB.
- CF_SEND_TELEMETRY=false and WRANGLER_SEND_METRICS=false.
- Managed /etc/claude-code/CLAUDE.md: prefer cf unless the project has a
  Wrangler config and no cloudflare.config.ts, where cf dev/build/deploy would
  rewrite package.json without asking.
- Templates persist ~/.config/cloudflare in a named volume so cf auth login
  survives rebuilds; the sandbox firewall template allows api.cloudflare.com
  and dash.cloudflare.com.
- CI verify checks cf, the managed CLAUDE.md, telemetry env and volume owner.
- READMEs recommend migrating Workers projects to cloudflare.config.ts.
# Conflicts:
#	.github/workflows/ci.yml
#	claude-code/.devcontainer/Dockerfile
Review follow-ups, each swapping custom code for a built-in feature:

- Move the cf guidance from a separate /etc/claude-code/CLAUDE.md into the
  claudeMd key of managed-settings.json, which master now uses for the
  browser guidance. Same precedence per the Claude Code docs; one file fewer.
- Install cf with npm --ignore-scripts. workerd's postinstall is what
  hard-linked its binary a second time, so one rm now frees the 133 MB, and
  no third-party install scripts run in the image build.
- Managed permissions.ask for `cf * --force*` / `cf * -f*`: cf aborts
  destructive commands without a terminal unless they carry that flag.
- README: recommend a least-privilege API token for sandbox agents and CI.
…th CI

- gh: oh-my-zsh's built-in gh plugin, which regenerates _gh from
  `gh completion` on each shell start.
- cf: `cf complete zsh >> ~/.zshrc` at build, as Cloudflare documents. The
  script asks cf for candidates, so cf upgrades don't make it stale.
- Verify checks both completions are registered (`_comps[gh]`, `_comps[cf]`).
- build-claude-code.yml's verify commands had fallen behind ci.yml's (no
  agent-browser, browser-skill or agent-browser config checks); they are now
  identical, so the post-merge build checks what the PR build checked.
@gatezh
gatezh merged commit f37b866 into master Oct 8, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant