From deb3428cfbb5dc65d47cc8827ea3ae8f2bd876c6 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 24 Apr 2026 15:24:47 +0700 Subject: [PATCH] feat: scheduled GitHub Actions workflow to fire Claude Code routine 4x daily --- .github/workflows/trigger-routine.yml | 72 +++++++++++++++++++++++++++ .gitignore | 5 ++ LICENSE | 21 ++++++++ README.md | 51 +++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 .github/workflows/trigger-routine.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.github/workflows/trigger-routine.yml b/.github/workflows/trigger-routine.yml new file mode 100644 index 0000000..09b3f8a --- /dev/null +++ b/.github/workflows/trigger-routine.yml @@ -0,0 +1,72 @@ +name: Trigger Claude Code Routine + +on: + schedule: + # UTC+7 (Asia/Ho_Chi_Minh) → UTC + - cron: '0 22 * * *' # 05:00 UTC+7 + - cron: '0 3 * * *' # 10:00 UTC+7 + - cron: '0 8 * * *' # 15:00 UTC+7 + - cron: '0 13 * * *' # 20:00 UTC+7 + workflow_dispatch: + inputs: + text: + description: 'Optional context text passed to the routine' + required: false + default: '' + +concurrency: + group: claude-code-routine-trigger + cancel-in-progress: false + +jobs: + fire: + name: Fire routine + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: POST /fire + env: + ROUTINE_FIRE_URL: ${{ secrets.ROUTINE_FIRE_URL }} + ROUTINE_FIRE_TOKEN: ${{ secrets.ROUTINE_FIRE_TOKEN }} + INPUT_TEXT: ${{ inputs.text }} + run: | + set -euo pipefail + + if [ -z "${ROUTINE_FIRE_URL:-}" ] || [ -z "${ROUTINE_FIRE_TOKEN:-}" ]; then + echo "::error::Missing secrets ROUTINE_FIRE_URL and/or ROUTINE_FIRE_TOKEN" + exit 1 + fi + + LOCAL_NOW=$(TZ=Asia/Ho_Chi_Minh date '+%Y-%m-%d %H:%M %Z') + if [ -n "${INPUT_TEXT}" ]; then + TEXT="${INPUT_TEXT}" + else + TEXT="Scheduled trigger at ${LOCAL_NOW} (run ${GITHUB_RUN_ID})" + fi + + PAYLOAD=$(jq -n --arg text "$TEXT" '{text: $text}') + + RESPONSE=$(curl -sS -w "\n%{http_code}" -X POST "$ROUTINE_FIRE_URL" \ + -H "Authorization: Bearer $ROUTINE_FIRE_TOKEN" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: experimental-cc-routine-2026-04-01" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + + HTTP_CODE=$(printf '%s' "$RESPONSE" | tail -n1) + BODY=$(printf '%s' "$RESPONSE" | sed '$d') + + echo "HTTP ${HTTP_CODE}" + echo "${BODY}" + + if [ "$HTTP_CODE" != "200" ]; then + echo "::error::Routine fire failed with HTTP ${HTTP_CODE}" + exit 1 + fi + + SESSION_URL=$(printf '%s' "$BODY" | jq -r '.claude_code_session_url // empty') + if [ -n "$SESSION_URL" ]; then + echo "### Claude Code session" >> "$GITHUB_STEP_SUMMARY" + echo "- Triggered at: ${LOCAL_NOW}" >> "$GITHUB_STEP_SUMMARY" + echo "- Session: ${SESSION_URL}" >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94da9f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.log +.env +.env.* +!.env.example diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad130e0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 tiennm99 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..31385f4 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# claude-code-routine-trigger + +GitHub Actions workflow that fires a [Claude Code routine](https://code.claude.com/docs/en/routines) four times a day via the [`/fire` API](https://platform.claude.com/docs/en/api/claude-code/routines-fire). + +Schedule (UTC+7, `Asia/Ho_Chi_Minh`): + +| Local time | UTC cron | +| ---------- | ------------- | +| 05:00 | `0 22 * * *` | +| 10:00 | `0 3 * * *` | +| 15:00 | `0 8 * * *` | +| 20:00 | `0 13 * * *` | + +Manual `workflow_dispatch` is supported with an optional `text` input for ad-hoc runs. + +## Setup + +1. Create a routine at (requires a Pro/Max/Team/Enterprise plan with Claude Code on the web enabled). +2. In the routine editor, add an **API** trigger and generate a token. Copy both the fire URL and the token — the token is shown once. +3. Add two repository secrets (`Settings → Secrets and variables → Actions`): + - `ROUTINE_FIRE_URL` — e.g. `https://api.anthropic.com/v1/claude_code/routines/trig_01ABo4hmfydBLFDgRMnKwEKy/fire` + - `ROUTINE_FIRE_TOKEN` — e.g. `sk-ant-oat01-...` +4. Enable Actions for the repo. Scheduled runs start on the next matching UTC tick. + +## Manual run + +`Actions → Trigger Claude Code Routine → Run workflow`. Leave `text` blank to get a timestamped default, or pass custom context (alert body, log line, etc.). + +## Request shape + +```bash +curl -X POST "$ROUTINE_FIRE_URL" \ + -H "Authorization: Bearer $ROUTINE_FIRE_TOKEN" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: experimental-cc-routine-2026-04-01" \ + -H "Content-Type: application/json" \ + -d '{"text": "Scheduled trigger ..."}' +``` + +Beta header pinned to `experimental-cc-routine-2026-04-01`. Two previous dated beta versions keep working while migrating — bump when Anthropic ships a new one. + +## Notes + +- GitHub cron runs can lag 5–15 min under load and are best-effort — acceptable for housekeeping-style routines, not for precise timing. +- Token is scoped to a single routine; a leak can only fire that one routine. +- Each POST creates a new session (no idempotency). Avoid retry loops that would multiply sessions. +- 429 responses include `Retry-After`; the workflow fails loud rather than retrying silently. + +## License + +MIT