Files

192 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# claude-code-routine-trigger-worker
Cloudflare Worker that fires a [Claude Code routine](https://code.claude.com/docs/en/routines) on a precise cron schedule via the [`/fire` API](https://platform.claude.com/docs/en/api/claude-code/routines-fire). 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](https://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](#recommended-cron-joborg).
>
> 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](#using-cloudflare-workers).
## Recommended: cron-job.org
[cron-job.org](https://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:
1. Sign up at [console.cron-job.org/signup](https://console.cron-job.org/signup).
2. Click **CREATE CRONJOB**.
3. **Common** tab:
- **Title**: anything (e.g. `claude-code-routine`)
- **URL**: paste the `/fire` URL from the Anthropic routine editor → *API trigger**URL* (looks like `https://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
4. **Advanced** tab → set **Request method** to `POST`.
5. **Headers** tab → add four headers:
| Key | Value |
| ------------------- | ---------------------------------------------- |
| `Authorization` | `Bearer sk-ant-oat01-...` (your routine token) |
| `anthropic-version` | `2023-06-01` |
| `anthropic-beta` | `experimental-cc-routine-2026-04-01` |
| `Content-Type` | `application/json` |
6. **Body** tab → select `Raw` and paste:
```json
{"text": "Scheduled trigger"}
```
7. **Notifications** tab (optional) → enable email on failure so you know if the token expires.
8. **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 `Authorization` header value. No redeploy.
- **Beta header**: when Anthropic ships a new dated `anthropic-beta` value, update the header. Older dated values keep working for a transition window per Anthropic's beta policy.
- **No retry on failure**: each `/fire` POST 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](https://github.com/tiennm99/claude-code-routine-trigger) | [claude-code-routine-cron](https://github.com/tiennm99/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.toml` ships 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
```bash
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:
```bash
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:
```toml
[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:
```toml
[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:
```bash
cp .dev.vars.example .dev.vars
# edit .dev.vars
npx wrangler dev --test-scheduled
```
Then trigger a scheduled run:
```bash
curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"
```
`.dev.vars` is gitignored — never commit it.
### Tests
```bash
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:
```bash
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 `/fire` POST creates a new Claude Code session — retrying multiplies sessions and burns quota. The worker logs failures and moves on.
- **Logs / traces**: `[observability]` is enabled in `wrangler.toml` with `head_sampling_rate = 1` and `invocation_logs = true` — every invocation produces a structured log + trace, retained per the [Workers Logs retention policy](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) (3 days on the free plan). View live with `npx 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.com` uses CF's standard cert verification.
- `worker.js` has no `fetch` HTTP handler — the worker is unreachable from the public internet, only fires on cron tick.
## License
[Apache-2.0](./LICENSE)