Claudinite scheduler #45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Claudinite per-repo task scheduler — the repo's ONLY cron | |
| # (per-project-scheduling DESIGN §3). A thin shim: all logic lives in the | |
| # vendored engine, so this file changes rarely (schema and behavior changes ride | |
| # the vendor refresh, not edits here). It runs hourly on a repo-hashed minute | |
| # constrained to :10–:50 — spreading the fleet, dodging GitHub's :00 stampede, | |
| # and staying clear of the hour boundary the slot math anchors on. | |
| # | |
| # The minute below is a placeholder; bootstrap/vendoring rewrites it to this | |
| # repo's stable hashed minute in :10–:50, computed by | |
| # `node .claudinite/shared/engine/scheduler/hash-minute.mjs <owner/repo>` | |
| # (scheduler-workflow-shape enforces the range; baselining re-derives the value to | |
| # catch drift). `workflow_dispatch` allows a manual run; the concurrency group | |
| # serializes runs so a slow run and its successor never overlap. | |
| name: Claudinite scheduler | |
| on: | |
| schedule: | |
| - cron: '24 * * * *' | |
| workflow_dispatch: | |
| concurrency: | |
| group: claudinite-scheduler | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write # read the repo + vendored engine; baselining's deliver() pushes the maintenance branch (agent-preprocessing §7/E4) | |
| issues: write # file / label / close dispatch issues | |
| pull-requests: write # baselining's deliver() opens the per-cycle maintenance PR and arms auto-merge | |
| actions: write # read the run ledger (last successful run) for due-slot math, AND | |
| # dispatch a workflow a task drives (store-release fires the release | |
| # orchestrator's daily leg) — creating a workflow_dispatch event needs | |
| # write, not read, and `read` silently 403s the POST | |
| jobs: | |
| schedule: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| - name: Evaluate schedule and dispatch due tasks | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| SCRAPER_API_KEY: ${{ secrets.SCRAPER_API_KEY }} | |
| run: node .claudinite/shared/engine/scheduler/run.mjs | |
| # A scheduled run reaches no one when it goes red, so escalate a whole-run | |
| # failure to a human-visible `workflow-failure` issue (DESIGN §3.6, | |
| # gha/scheduled-failure-escalation). Converges to ONE open issue by a fixed | |
| # title (create-or-comment), and ensures the label first since a first-run | |
| # failure may precede the scheduler's own label-ensure. | |
| report-failure: | |
| needs: schedule | |
| if: failure() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| steps: | |
| - uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const title = 'Claudinite scheduler run failed'; | |
| await github.rest.issues.createLabel({ owner, repo, name: 'workflow-failure', color: 'b60205', description: 'Claudinite scheduler: a scheduler run or task failed' }).catch(() => {}); | |
| const run = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`; | |
| const body = `The Claudinite scheduler run failed: ${run}`; | |
| const found = await github.rest.search.issuesAndPullRequests({ q: `repo:${owner}/${repo} is:issue is:open in:title "${title}"` }); | |
| if (found.data.total_count > 0) await github.rest.issues.createComment({ owner, repo, issue_number: found.data.items[0].number, body }); | |
| else await github.rest.issues.create({ owner, repo, title, labels: ['workflow-failure'], body }); |