# The red watcher as its own workflow, on workflow_run, so the copy on master watches EVERY branch's ci run whatever # ci.yml that branch carries: GitHub runs a workflow_run workflow from the default branch only, and the branch's own # ci.yml never enters it (7 October 2026: the inline `red` job of ci.yml was conditioned on master and release-*, and # a feature branch would have waited for a merge of master before its reds were posted at all). # # One line per failed, cancelled or timed-out run (tools/ci/red-watch.mjs record, idempotent per run attempt) to /srv/ci-red/red.jsonl on the # box; the box's igneum-ci-red.timer posts each new line once to the hidden updates channel, naming the branch, the # commit, the red check and the pushing author. Runs on the box's own runner (not a GitHub-hosted machine: the billing # block of 6 October 2026, 18:37Z to 20:10Z, failed every hosted job at start and nobody was told). Never blocks a # release: it reads the run, writes one line, and ends. name: ci-red on: workflow_run: workflows: [ci] types: [completed] jobs: red: name: red watcher (every branch; one line per failed run, with the branch, commit, red check and pushing author, to the updates channel and the box file) # failure, and since 7 October 2026 (17:2x UK) cancelled and timed_out too: a job that hangs into its timeout-minutes or a run # someone cancels is a run that never answered, and a lane reads it like a red (tools/ci/red-watch.mjs names the kind) if: ${{ github.event.workflow_run.conclusion == 'failure' || github.event.workflow_run.conclusion == 'cancelled' || github.event.workflow_run.conclusion == 'timed_out' }} # the label ci-red is on igneum-build-1 only (added through the runners API on 7 October 2026; the default of # RUNNER_LABELS in provision.sh carries it): the record file and the poster (igneum-ci-red.timer, the webhook file) # live on that box, and the pool label igneum-build-1 is shared with igneum-build-2 since the same day runs-on: [self-hosted, linux, x64, ci-red] timeout-minutes: 5 permissions: actions: read # the failed run's jobs API (the first real red run, 21:19Z on 6 October: the default token answered 403 and the line carried no step) contents: read steps: - uses: actions/checkout@v4 with: sparse-checkout: tools/ci - name: record the failed run (one line, the branch, the commit, the failed jobs and their first failed step from the run's own API, the pushing author) env: GITHUB_TOKEN: ${{ github.token }} RED_WATCH_RUN_ID: ${{ github.event.workflow_run.id }} RED_WATCH_ATTEMPT: ${{ github.event.workflow_run.run_attempt }} RED_WATCH_WORKFLOW: ${{ github.event.workflow_run.name }} RED_WATCH_CONCLUSION: ${{ github.event.workflow_run.conclusion }} RED_WATCH_BRANCH: ${{ github.event.workflow_run.head_branch }} RED_WATCH_SHA: ${{ github.event.workflow_run.head_sha }} RED_WATCH_EVENT: ${{ github.event.workflow_run.event }} RED_WATCH_URL: ${{ github.event.workflow_run.html_url }} RED_WATCH_ACTOR: ${{ github.event.workflow_run.actor.login }} RED_WATCH_TITLE: ${{ github.event.workflow_run.head_commit.message }} RED_WATCH_AUTHOR: ${{ github.event.workflow_run.head_commit.author.name }} run: node tools/ci/red-watch.mjs record --file /srv/ci-red/red.jsonl