10 KiB
claude-code-routine-trigger-worker
Cloudflare Worker that fires a Claude Code routine on a precise cron schedule via the /fire API. Runs on Cloudflare's free tier — no servers, no GitHub minutes, no Docker host.
Tip
Anthropic's routine editor now ships a built-in cron trigger — it runs on Anthropic's infra, no setup. Use it first.
If you need an external scheduler (Anthropic cron disabled, custom payloads, audit trail outside Anthropic), I recommend cron-job.org — free, no infra, ~2-min setup. I tried it on this exact routine and am satisfied, so I've parked this worker (cron triggers are commented out in
wrangler.toml) to keep my CF cron-trigger quota free for other projects. See Recommended: cron-job.org.This worker is still a valid path if you specifically want secrets in CF / logs in CF / schedule in code review. See Using Cloudflare Workers.
Recommended: cron-job.org
cron-job.org is a free hosted cron service with a clean dashboard, per-fire history, and 1-min granularity on the free tier. Setup:
-
Sign up at console.cron-job.org/signup.
-
Click CREATE CRONJOB.
-
Common tab:
- Title: anything (e.g.
claude-code-routine) - URL: paste the
/fireURL from the Anthropic routine editor → API trigger → URL (looks likehttps://api.anthropic.com/v1/claude_code/routines/trig_.../fire) - Schedule: pick your timezone and the times to fire — cron-job.org accepts both UI selectors and raw cron syntax
- Title: anything (e.g.
-
Advanced tab → set Request method to
POST. -
Headers tab → add four headers:
Key Value AuthorizationBearer sk-ant-oat01-...(your routine token)anthropic-version2023-06-01anthropic-betaexperimental-cc-routine-2026-04-01Content-Typeapplication/json -
Body tab → select
Rawand paste:{"text": "Scheduled trigger"} -
Notifications tab (optional) → enable email on failure so you know if the token expires.
-
Save.
Each fire shows up in History with status code and response body — open claude_code_session_url from the response JSON to watch the run.
Operational notes:
- Token rotation: edit the cronjob → swap the
Authorizationheader value. No redeploy. - Beta header: when Anthropic ships a new dated
anthropic-betavalue, update the header. Older dated values keep working for a transition window per Anthropic's beta policy. - No retry on failure: each
/firePOST creates a new Claude Code session, so retrying would multiply sessions and burn quota. cron-job.org's default is one attempt per fire, which is what you want. - Limits: cron-job.org's free tier allows up to 50 cronjobs and unlimited executions at 1-min granularity — way more than enough for routine triggering.
Why this vs the siblings
| claude-code-routine-trigger | claude-code-routine-cron | claude-code-routine-trigger-worker (this) | |
|---|---|---|---|
| Runs on | GitHub Actions runners | your infra (Docker host, k8s, NAS, RPi) | Cloudflare edge |
| Cost | free (within GitHub minutes) | minimal (your infra) | free (CF free tier) |
| Cron precision | ±30 min – 2 h, can drop runs | sub-second | within ~15 sec |
| Setup | fork + 2 repo secrets | env vars + Docker | wrangler deploy + 2 secrets |
| Audit trail | GitHub Actions runs page | container stdout | CF dashboard / wrangler tail |
Using Cloudflare Workers
The default
wrangler.tomlships with the[triggers]block commented out — see the note in the file. To activate this worker, uncomment the block (and edit the schedule) before deploying.
Quickstart
git clone https://github.com/tiennm99/claude-code-routine-trigger-worker
cd claude-code-routine-trigger-worker
pnpm install
# Upload secrets (from Anthropic routine editor → API trigger)
echo -n 'https://api.anthropic.com/v1/claude_code/routines/trig_.../fire' \
| npx wrangler secret put ROUTINE_FIRE_URL
echo -n 'sk-ant-oat01-...' \
| npx wrangler secret put ROUTINE_FIRE_TOKEN
# Uncomment [triggers].crons in wrangler.toml, edit the schedule + timezone, then:
npx wrangler deploy
Tail logs:
npx wrangler tail
A successful fire logs a JSON line with session_url. Open it to watch the run.
Environment variables
Configured in wrangler.toml ([vars] for plain values) or via wrangler secret put (for secrets).
| Name | Type | Required | Default | Notes |
|---|---|---|---|---|
ROUTINE_FIRE_URL |
secret | yes | — | Anthropic /fire endpoint. From routine editor → API trigger. |
ROUTINE_FIRE_TOKEN |
secret | yes | — | sk-ant-oat01-... per-routine token. Shown once in the editor. |
TEXT_TEMPLATE |
var | no | Scheduled trigger at {LocalTime} |
Token-substitution template. See Templates. |
TZ |
var | no | UTC |
IANA tz name (Asia/Ho_Chi_Minh, America/New_York, …). Used for {LocalTime} formatting. |
Customize the schedule
Uncomment and edit wrangler.toml [triggers].crons — Cloudflare requires literal cron expressions (same constraint as GitHub Actions). To change when the routine fires:
[triggers]
crons = [
"0 0-17,22-23 * * *", # UTC+7: 00:00 + 05:00..23:00 hourly
]
Then redeploy: npx wrangler deploy.
Tips:
- Cron runs in UTC. Convert your local time:
UTC = local − offset(e.g. 09:00 UTC+7 → 02:00 UTC →0 2 * * *). - Validate expressions at https://crontab.guru/.
- Free tier limit: 5 cron expressions per worker. Default config uses 1.
- Standard 5-field syntax — supports
*,,,-,/. Comma lists (22,23,0,1,2) and ranges (3-7) let you cram many fires into one expression.
Templates
TEXT_TEMPLATE supports these {Token} substitutions, rendered per fire. Unknown tokens are left intact in the output.
| Token | Example |
|---|---|
{ISO} |
2026-05-09T03:19:00.000Z |
{LocalTime} |
2026-05-09 10:19 GMT+7 |
{Cron} |
19 3 * * * — the expression that fired |
Example:
[vars]
TEXT_TEMPLATE = "Daily digest at {LocalTime} (cron {Cron})"
TZ = "America/New_York"
Local development
Copy .dev.vars.example to .dev.vars and fill in your routine credentials:
cp .dev.vars.example .dev.vars
# edit .dev.vars
npx wrangler dev --test-scheduled
Then trigger a scheduled run:
curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"
.dev.vars is gitignored — never commit it.
Tests
pnpm test
Vitest runs in the Workers runtime via @cloudflare/vitest-pool-workers. Tests mock fetch, so they consume no Anthropic quota.
Secret rotation
wrangler secret put overwrites silently. Rotate by re-running it with the new value:
echo -n 'sk-ant-oat01-newvalue' | npx wrangler secret put ROUTINE_FIRE_TOKEN
The change takes effect on the next deploy or within a few seconds via the live config.
Beta header
The request pins anthropic-beta: experimental-cc-routine-2026-04-01 (constant in worker.js). When Anthropic ships a new dated beta, bump it via a release. Older dated values keep working for a transition window per Anthropic's beta policy.
Operational notes
- Time accuracy: cron precision on CF Workers is within ~15 seconds — adequate for routine triggering.
- No retry: each
/firePOST creates a new Claude Code session — retrying multiplies sessions and burns quota. The worker logs failures and moves on. - Logs / traces:
[observability]is enabled inwrangler.tomlwithhead_sampling_rate = 1andinvocation_logs = true— every invocation produces a structured log + trace, retained per the Workers Logs retention policy (3 days on the free plan). View live withnpx wrangler tail, or browse history in the CF dashboard under Workers & Pages → your worker → Logs. - Cost: scheduled handlers count against the Workers Free plan's 100k requests/day budget. 5 daily fires × 30 days = 150 requests/month — negligible.
Security
- The token is per-routine: a leak only fires that one routine.
- Secrets live in CF's encrypted secret store, never in the bundle, never in logs (verified by tests).
- TLS to
api.anthropic.comuses CF's standard cert verification. worker.jshas nofetchHTTP handler — the worker is unreachable from the public internet, only fires on cron tick.