{"id":51317022,"url":"https://github.com/tiennm99/claude-code-routine-cron","last_synced_at":"2026-07-01T08:31:04.127Z","repository":{"id":357141292,"uuid":"1233152906","full_name":"tiennm99/claude-code-routine-cron","owner":"tiennm99","description":"Self-hosted Go daemon firing Claude Code routines on cron via /fire API. Docker on GHCR. See -trigger \u0026 -worker.","archived":false,"fork":false,"pushed_at":"2026-05-11T13:09:39.000Z","size":21,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-11T15:13:09.525Z","etag":null,"topics":["claude-code","cron","daemon","docker","go","scheduler","self-hosted"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tiennm99.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-08T16:42:06.000Z","updated_at":"2026-05-11T13:10:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tiennm99/claude-code-routine-cron","commit_stats":null,"previous_names":["tiennm99/claude-code-routine-cron"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/tiennm99/claude-code-routine-cron","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiennm99%2Fclaude-code-routine-cron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiennm99%2Fclaude-code-routine-cron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiennm99%2Fclaude-code-routine-cron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiennm99%2Fclaude-code-routine-cron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tiennm99","download_url":"https://codeload.github.com/tiennm99/claude-code-routine-cron/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiennm99%2Fclaude-code-routine-cron/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34999791,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","cron","daemon","docker","go","scheduler","self-hosted"],"created_at":"2026-07-01T08:30:59.387Z","updated_at":"2026-07-01T08:31:04.118Z","avatar_url":"https://github.com/tiennm99.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# claude-code-routine-cron\n\nSelf-hosted Go daemon 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). Packaged as a multi-arch Docker image on GHCR.\n\n\u003e [!TIP]\n\u003e **Anthropic's routine editor now ships a built-in cron trigger** — that runs on Anthropic's infra, no setup. Use it first.\n\u003e\n\u003e This repo is for users who explicitly want **self-hosted scheduling**: precise timing (no GitHub Actions delays), behind-firewall, on-prem audit trail, or to integrate with their own infra.\n\n## Why this vs `claude-code-routine-trigger`\n\n|                  | [claude-code-routine-trigger](https://github.com/tiennm99/claude-code-routine-trigger) | **claude-code-routine-cron** (this repo) |\n| ---------------- | -------------------------------------------------------------------------------------- | ---------------------------------------- |\n| Runs on          | GitHub Actions runners                                                                 | your infra (Docker host, k8s, NAS, RPi)  |\n| Cost             | free (within GitHub minutes)                                                           | minimal (your infra)                     |\n| Cron precision   | ±30 min – 2 h, can drop runs                                                           | sub-second                               |\n| Setup            | fork + 2 repo secrets                                                                  | 2 env vars + cron + Docker               |\n| Secret storage   | GitHub repo secrets                                                                    | host env / Docker secrets / k8s Secret   |\n| Audit trail      | GitHub Actions runs page                                                               | container stdout                         |\n\n## Quickstart\n\n```bash\ndocker run -d --name claude-routine \\\n  --restart unless-stopped \\\n  -e ROUTINE_FIRE_URL='https://api.anthropic.com/v1/claude_code/routines/trig_.../fire' \\\n  -e ROUTINE_FIRE_TOKEN='sk-ant-oat01-...' \\\n  -e CRON_SCHEDULE='0 9 * * *;0 18 * * *' \\\n  -e TZ='Asia/Ho_Chi_Minh' \\\n  ghcr.io/tiennm99/claude-code-routine-cron:latest\n```\n\nTail logs:\n\n```bash\ndocker logs -f claude-routine\n```\n\nA successful fire logs a JSON line with `claude_code_session_url`. Open it to watch the run.\n\n## Environment variables\n\n| Name                 | Required | Default                                  | Notes |\n| -------------------- | :------: | ---------------------------------------- | ----- |\n| `ROUTINE_FIRE_URL`   | yes      | —                                        | Anthropic `/fire` endpoint. From routine editor → API trigger. |\n| `ROUTINE_FIRE_TOKEN` | yes      | —                                        | `sk-ant-oat01-...` per-routine token. Shown once in the editor. |\n| `CRON_SCHEDULE`      | yes      | —                                        | One or more standard 5-field cron expressions. Split on `;` or newlines. |\n| `TZ`                 | no       | `UTC`                                    | IANA tz name (`Asia/Ho_Chi_Minh`, `America/New_York`, …). Cron evaluates in this zone. |\n| `TEXT_TEMPLATE`      | no       | `Scheduled trigger at {{.LocalTime}}`    | Go [text/template](https://pkg.go.dev/text/template); see *Templates*. |\n| `LOG_LEVEL`          | no       | `info`                                   | `debug`, `info`, `warn`, `error`. |\n\nValidation is fail-fast: missing required vars or malformed crons/timezones/templates cause the daemon to exit with a clear error before any HTTP traffic.\n\n## Multiple schedules\n\nUse `;` (or newlines, in YAML block scalars) to register multiple crons:\n\n```yaml\nenvironment:\n  CRON_SCHEDULE: |\n    0 9 * * *\n    0 13 * * *\n    0 18 * * *\n```\n\nEach schedule fires independently; all use the same `TEXT_TEMPLATE`, URL, and token.\n\n## Templates\n\nThe `TEXT_TEMPLATE` env var is rendered as a Go `text/template` per fire with these variables:\n\n| Var          | Type        | Example                          |\n| ------------ | ----------- | -------------------------------- |\n| `.Now`       | `time.Time` | UTC time of the fire             |\n| `.LocalTime` | `string`    | `2026-05-08 23:30 +07`           |\n| `.Cron`      | `string`    | `0 9 * * *` — the expression that fired |\n\nExample:\n\n```bash\n-e TEXT_TEMPLATE='Daily digest at {{.LocalTime}} (cron {{.Cron}})'\n```\n\n## docker-compose\n\nSee [`docker-compose.example.yml`](./docker-compose.example.yml) and [`.env.example`](./.env.example):\n\n```bash\ncp docker-compose.example.yml docker-compose.yml\ncp .env.example .env       # then edit secrets\ndocker compose up -d\n```\n\n## Security\n\n- The token is **per-routine**: a leak only fires that one routine.\n- Token never appears in logs (verified by tests).\n- No retry on failure — each `/fire` POST creates a new session, retries would multiply sessions and burn quota.\n- Image is `gcr.io/distroless/static-debian12:nonroot` — no shell, no package manager, runs as UID 65532.\n- TLS to `api.anthropic.com` uses standard Go cert verification.\n\n## Beta header\n\nThe request pins `anthropic-beta: experimental-cc-routine-2026-04-01`. When Anthropic ships a new dated beta, bump it via a new release. Older dated values keep working for a transition window per Anthropic's beta policy.\n\n## Operational notes\n\n- **Time accuracy**: cron correctness depends on the host clock. Run NTP / sync on the Docker host.\n- **Restart policy**: use `--restart unless-stopped` (or k8s `Deployment`) — the daemon does not self-restart on panic.\n- **Pin tags in production**: prefer `:vX.Y.Z` over `:latest`.\n- **No idempotency**: avoid retry loops in upstream automation that POSTs the same alert twice.\n\n## Build from source\n\n```bash\ngit clone https://github.com/tiennm99/claude-code-routine-cron\ncd claude-code-routine-cron\ngo build .\n./claude-code-routine-cron     # uses env vars from your shell\n```\n\nTests:\n\n```bash\ngo test -race -cover ./...\n```\n\n## License\n\n[Apache-2.0](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftiennm99%2Fclaude-code-routine-cron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftiennm99%2Fclaude-code-routine-cron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftiennm99%2Fclaude-code-routine-cron/lists"}