# Cloudflare Workers Publish Anti-Patterns

Gotchas discovered during rangebar-patterns deployment (2026-02-18). Severity ratings indicate impact of hitting each issue without prior knowledge.

---

## CFW-01: Using Cloudflare Pages (DEPRECATED) [HIGH]

**Symptom**: `wrangler pages deploy` produces deprecation warnings or fails.

**Root cause**: Cloudflare deprecated Pages in April 2025 in favor of Workers with Static Assets.

**Fix**: Use Workers with `[assets]` section in wrangler.toml. No Worker script needed.

```toml
# WRONG — Pages (deprecated)
# wrangler pages deploy ./dist

# RIGHT — Workers Static Assets
name = "my-site"
compatibility_date = "2026-02-18"
[assets]
directory = "."
```

---

## CFW-02: 1Password Service Account Cannot Create Items [HIGH]

**Symptom**: `op item create` fails with permission error when using `OP_SERVICE_ACCOUNT_TOKEN`.

**Root cause**: 1Password service accounts have READ + UPDATE access only. They cannot CREATE new items.

**Fix**: Create the 1Password item manually first (biometric `op` CLI or web UI), then scripts read from it via the service account.

```bash
# Step 1: Create item (interactive, requires biometric)
op item create \
  --category "API Credential" \
  --title "Cloudflare Workers - my-project" \
  --vault "Claude Automation"

# Step 2: Script reads via service account (headless, no biometric)
OP_SERVICE_ACCOUNT_TOKEN="$(cat ~/.claude/.secrets/op-service-account-token)" \
  op item get "{item-id}" --vault "Claude Automation" --fields "credential" --reveal
```

See [1Password setup guide](./onep-credential-setup.md) for full provisioning steps.

---

## CFW-03: Missing --reveal for CONCEALED Fields [HIGH]

**Symptom**: `op item get` returns a masked placeholder string instead of the actual token value.

**Root cause**: 1Password CONCEALED fields require the `--reveal` flag to return the actual value.

**Fix**: Always use `--reveal` when fetching API tokens or passwords.

```bash
# WRONG — returns masked placeholder
op item get "$ITEM_ID" --vault "Claude Automation" --fields "credential"

# RIGHT — returns actual token value
op item get "$ITEM_ID" --vault "Claude Automation" --fields "credential" --reveal
```

TEXT fields (like `account_id`) do NOT need `--reveal`.

---

## CFW-04: SC2155 — export VAR=$(cmd) Masks Return Values [MEDIUM]

**Symptom**: Script continues past a failed credential fetch because `export` masks the non-zero exit code.

**Root cause**: ShellCheck SC2155. `export VAR=$(cmd)` always returns 0 (the export succeeds) even if `$(cmd)` fails.

**Fix**: Split into two statements.

```bash
# WRONG — exit code of op is masked
export CLOUDFLARE_API_TOKEN=$(op item get "$ITEM_ID" --fields "credential" --reveal)

# RIGHT — preserves exit code
CLOUDFLARE_API_TOKEN=$(op item get "$ITEM_ID" --fields "credential" --reveal)
export CLOUDFLARE_API_TOKEN
```

---

## CFW-05: Bash 4+ Syntax on macOS [LOW]

**Symptom**: `${var^^}` (uppercase conversion) fails with "bad substitution" on macOS.

**Root cause**: macOS ships with bash 3.2. `${var^^}` is a bash 4+ feature.

**Fix**: Use `tr` for portable case conversion.

```bash
# WRONG — bash 4+ only
gen_upper="${gen_dir^^}"

# RIGHT — works on bash 3 and zsh
gen_upper=$(echo "$gen_dir" | tr '[:lower:]' '[:upper:]')
```

---

## CFW-06: workers.dev Subdomain Is Not Predictable [MEDIUM]

**Symptom**: Guessing the URL format leads to 404.

**Root cause**: The workers.dev URL is `{worker-name}.{account-slug}.workers.dev`. The `account-slug` is auto-generated by Cloudflare (e.g., `terry-301`), not derivable from account fields.

**Fix**: Discover your account slug after first deploy:

```bash
npx wrangler whoami
# Or check: Cloudflare dashboard > Workers & Pages > Overview
```

Hardcode the full URL in your deploy script's success message and project docs.

---

## CFW-07: workers.dev Subdomain Not Registered [HIGH]

**Symptom**: Deploy succeeds but URL returns 403 Forbidden or "workers.dev is not registered."

**Root cause**: First-time Cloudflare accounts must explicitly enable the workers.dev route.

**Fix**:

1. Go to <https://dash.cloudflare.com> > **Workers & Pages**
2. Look for workers.dev subdomain section
3. Click to enable/register your subdomain
4. Re-deploy

---

## CFW-08: SSL/TLS Handshake Failure from macOS curl [LOW]

**Symptom**: `curl https://{name}.{slug}.workers.dev/` fails with SSL handshake error. URL works fine in browsers.

**Root cause**: macOS bundles LibreSSL 3.3.6 which may fail TLS negotiation with Cloudflare's edge servers.

**Fix**: Verify in a browser. Do NOT use `curl -k` (disables certificate verification entirely).

```bash
# If you must use CLI:
curl --tlsv1.2 "https://{name}.{slug}.workers.dev/"
# Or just open in browser — this is a non-issue for actual users
```

---

## CFW-09: Overcomplicating wrangler.toml [MEDIUM]

**Symptom**: Copying wrangler.toml examples from docs that include `main`, `routes`, `zone_id`, KV bindings.

**Root cause**: Most examples are for dynamic Workers. Static-only sites need almost nothing.

**Fix**: Minimal config:

```toml
name = "my-project"
compatibility_date = "2026-02-18"

[assets]
directory = "."
```

Three fields total. See [wrangler setup guide](./wrangler-setup.md).

---

## CFW-10: Running wrangler from Wrong Directory [HIGH]

**Symptom**: `wrangler deploy` says "no wrangler.toml found" or deploys wrong files.

**Root cause**: wrangler looks for wrangler.toml in the current working directory.

**Fix**: Always `cd` to the directory containing wrangler.toml before deploy.

```bash
# WRONG — runs from project root
npx wrangler deploy

# RIGHT — cd to publish directory first
cd "$PUBLISH_DIR"
npx wrangler deploy 2>&1
```

---

## CFW-11: Excessive Token Permissions [MEDIUM]

**Symptom**: Token created with too many permissions (Zone edit, DNS, etc.).

**Root cause**: Following docs that suggest broader permissions for complex setups.

**Fix**: For static assets on workers.dev, the minimum is:

- **Permission**: Account > Workers Scripts > Edit
- **Account resources**: Include > (your specific account)
- No Zone, DNS, or custom domain permissions needed

---

## CFW-12: Deploying Git LFS Pointers Instead of Files [HIGH]

**Symptom**: Deploy succeeds, but HTML files show as small text files with LFS pointer data.

**Root cause**: Git LFS stores pointers in the repo. `git lfs pull` must be run first.

**Fix**:

```bash
# Check if files are pointers
head -1 results/published/gen800/XRPUSDT_750/equity_plot.html
# If it starts with "version https://git-lfs.github.com" — it's a pointer

# Pull actual files
git lfs pull

# Then deploy
bash scripts/publish_findings.sh
```

---

## CFW-13: Tera Template Conflict in mise TOML [MEDIUM]

**Symptom**: `mise run publish:site` fails with "Variable not found in context."

**Root cause**: mise uses the Tera templating engine which interprets `{{}}`, `{%}`, and `#}` in TOML `run` strings.

**Fix**: Keep TOML task definitions simple. Move bash logic to standalone `.sh` files.

```toml
# WRONG — Tera will choke on bash syntax
["publish:site"]
run = '''
for f in "${FILES[@]}"; do
    echo "processing #${f}"
done
'''

# RIGHT — mise just invokes the script
["publish:site"]
run = "bash scripts/publish_static.sh"
```

---

## CFW-14: Pipe Subshell Data Loss in while-read [MEDIUM]

**Symptom**: Variables set inside a `while read` loop are empty after the loop ends.

**Root cause**: `find ... | while read` runs the `while` in a subshell. Subshell variable changes do not propagate.

**Fix**: Use process substitution.

```bash
# WRONG — subshell, FOUND always 0 after loop
find . -name "*.html" | while IFS= read -r f; do
    FOUND=$((FOUND + 1))
done
echo "$FOUND"  # Always 0

# RIGHT — process substitution, runs in current shell
while IFS= read -r f; do
    FOUND=$((FOUND + 1))
done < <(find . -name "*.html")
echo "$FOUND"  # Correct count
```

---

## CFW-15: No Directory Listing Page [LOW]

**Symptom**: Workers static assets serves files by exact path but root URL returns 404 or blank page.

**Root cause**: Workers static assets does not auto-generate directory listings.

**Fix**: Auto-generate an `index.html` before each deploy that lists all published files. See the bundled [deploy script template](../scripts/publish_static.sh) for the index generation logic.
