feat(runners): route on fleet liveness, drop rerun-to-hosted branch - #86
Conversation
Change the prefer-self-hosted candidate test from idleness (status online AND busy false) to liveness (status online): a busy online runner proves the fleet is alive, and GitHub natively queues the job until a matching runner frees up (failing only after 24h queued, per official routing docs). Only a fully offline fleet now falls back to the paid hosted route. Delete the run_attempt > 1 -> hosted branch and the RUN_ATTEMPT input: a re-run reuses the previous attempt's successful selector output (official re-run docs), so the hack only converted re-runs into paid hosted work jobs. Rename idle-runner-count -> online-runner-count and the idle / no-idle-runner reasons -> online / no-online-runner so outputs state what is actually observed. Drop the now-unconsumed busy field from inventory validation. Regenerate the vendored bundle and update the README selector contract, including the one-preflight-per-workflow guidance. Part of melodic-software/github-iac#79 (Epic #78 W1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EPDbXgonTuFwFwdTtHaCmw
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_22d40f7c-a1b3-4602-8a86-02e22e85c4d4) |
|
Claude finished @kyle-sexton's task in 2m 29s —— View job PR Review
SummarySolid, well-scoped PR. The liveness-over-idleness routing change is correct and the implementation is internally consistent. The rerun-to-hosted removal is well-justified, and the refactoring is thorough across all layers (source, vendored bundle, tests, docs). Findings1. ci-workflows/.github/scripts/select-runner.cjs Lines 266 to 267 in 83f90e1 The old schema rejection 2. README re-run claim is accurate for one scenario but imprecise for another
This is true for Re-run failed jobs (the selector, if successful on attempt 1, is skipped and its outputs are inherited). It is not true for Re-run all jobs — GitHub re-runs the selector fresh in that case. Since 3. Token-mint guard removal — implicit coupling ci-workflows/.github/workflows/select-runner.yml Lines 83 to 84 in 83f90e1 Removing 4. Test split of ci-workflows/.github/scripts/select-runner.test.cjs Lines 374 to 406 in 83f90e1 The old test mixed offline + busy assertions into one case. The split into 5. ci-workflows/.github/scripts/select-runner.test.cjs Lines 517 to 525 in 83f90e1 The old test was semantically wrong after the behavior change (it tested 6. Output contract — breaking change acknowledged
VerdictApprove. No correctness bugs, no security regressions, no CLAUDE.md violations. The one documentation nuance (finding 2) is minor and worth a pass on if the team prefers brevity. Everything else is clean. |
There was a problem hiding this comment.
Stale comment
Risk: medium. Cursor Bugbot did not complete successfully (check skipped due to usage limit), so I cannot approve on automated review signals. Human review is needed; no reviewers were assigned because no eligible reviewers exist beyond the PR author in this repository.
Sent by Cursor Approval Agent: Pull Request Router and Approver
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83f90e179d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The conformance assert still expected reason `rerun` on run_attempt > 1, which the selector no longer emits. The public-repository guard fires on every attempt; a full re-run now makes a fresh liveness decision instead of forcing hosted. Clarify the README re-run wording to distinguish the re-run-failed-jobs (output reuse) and re-run-all-jobs (fresh decision) paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EPDbXgonTuFwFwdTtHaCmw
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_77d97d02-10de-420c-a513-a71929b914ec) |
|
Claude finished @kyle-sexton's task in 2m 28s —— View job PR Review
SummaryWell-scoped, internally consistent PR. The liveness-over-idleness change is semantically correct and the rerun hack removal is well-justified. All five layers (source, vendored bundle, tests, conformance, docs) move in lockstep. No correctness bugs, no security regressions, no CLAUDE.md violations. One prior review comment to address: the Codex P2 finding about Findings1. ci-workflows/.github/scripts/select-runner.cjs Lines 256 to 281 in d1e14eb The old 2. Re-run semantics in README are now correctly scoped Lines 158 to 160 in d1e14eb
The previous Claude review (finding 2) flagged that the re-run sentence over-promised. This PR's README now correctly distinguishes the two re-run modes. The text is accurate: "re-run failed jobs" skips the selector and inherits its previous outputs; "re-run all jobs" re-executes the selector, which now makes a fresh liveness decision rather than being forced to hosted. ✅ 3. Observer-token guard removal — correct and not a security concern ci-workflows/.github/workflows/select-runner.yml Lines 83 to 101 in d1e14eb Removing 4. Old: The conformance PR runs from a fork/public context where the public-repository guard fires before any inventory check, so 5. The old 6. Test coverage for the behavioral change ci-workflows/.github/scripts/select-runner.test.cjs Lines 310 to 320 in d1e14eb New test 7. Vendored bundle sync — verified The inline bundle in VerdictApprove. No blocking issues. The liveness routing change is correct, the rerun hack removal is justified, and the implementation is thorough across all layers. The Codex P2 concern about conformance is already resolved in this PR. |
There was a problem hiding this comment.
Risk: medium. Cursor Bugbot did not complete successfully (check skipped due to usage limit), so I cannot approve on automated review signals. Human review is needed; no reviewers were assigned because no eligible reviewers exist beyond the PR author in this repository.
Sent by Cursor Approval Agent: Pull Request Router and Approver
## What Applies GitHub Actions volume controls to this repo's own CI entry-point workflows (W4 of the Actions-minutes epic). - **Concurrency keyed on `head_ref || run_id`** in `ci`, `pr-title`, `selector-conformance`, and `claude-review-self` (the latter previously had no concurrency block). `github.head_ref` is only set on `pull_request`, so a new push to a PR cancels the superseded run, while `push`/`workflow_dispatch` runs get a unique `run_id` group and are never cancelled — the previous `github.ref`-based keys could cancel in-flight default-branch runs. - **Dead `merge_group` triggers removed** from `ci` and `pr-title`: merge queue is not available on the org's plan, so these runs can never fire. The root-CI `merge_group` assertion in `select-runner.test.cjs` is updated in the same commit; `node --test .github/scripts/*.test.cjs` passes 180/180. The README's canonical consumer block (documenting `merge_group` for queue-enabled consumers) is left intact, as are the merge-queue guards inside the `semantic-pr` reusable workflow — those support external callers and are inert without a queue. - **Dependabot `open-pull-requests-limit` lowered to 5.** Weekly interval, all-actions grouping, and 7-day cooldown were already in place. ## Why Cuts wasted runner minutes from superseded PR runs and never-firing triggers without touching any reusable `workflow_call` contract — callers own concurrency for reusable workflows, so none of them gain a concurrency block. No `select-runner` call, selector source, or `runs-on` expression is modified (avoids overlap with #86). Part of melodic-software/github-iac#82 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01EPDbXgonTuFwFwdTtHaCmw <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Workflow trigger and concurrency tuning only; no reusable workflow_call contracts, runner selection, or application logic changed. > > **Overview** > Tightens **GitHub Actions** usage on this repo’s entry-point workflows: **concurrency** now groups on `github.event.pull_request.number` with `github.run_id` as the non-PR fallback, so new PR pushes cancel superseded runs while **main** / **workflow_dispatch** runs are not lumped together under `github.ref`. **`claude-review-self`** gets the same pattern (it previously had no concurrency block). > > **`merge_group`** is dropped from **`ci`** and **`pr-title`** triggers because merge queue isn’t available here; comments point queue-enabled consumers at the README pattern. The root-CI contract test no longer requires `merge_group` in `ci.yml`. > > **Dependabot** `open-pull-requests-limit` goes from **10** to **5** (weekly schedule, grouping, and cooldown unchanged). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0da615b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary Bumps the `select-runner` reusable-workflow pin in this repo's own CI to the liveness routing revision of the central selector (melodic-software/ci-workflows#86, commit `3415de3`). That revision was already approved owner-scoped in the runner policy by #100, so this PR changes only the single consumer pin line in `.github/workflows/ci.yml` — the `zizmor.yml` reference and `components/runner-policy/` are untouched. Part of melodic-software/github-iac#79 (epic #78). ## Verification - `node --test components/runner-policy/runner-policy.test.mjs`: 95 pass, 0 fail - `node --test components/lefthook-dotnet/dotnet-format-staged.test.mjs`: 12 pass, 0 fail - `GITHUB_REPOSITORY=melodic-software/standards npm run lint:runner-policy`: "Runner policy passed." - `git diff origin/main --stat`: only the single ci.yml line changed 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01EPDbXgonTuFwFwdTtHaCmw <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Single pinned SHA bump for CI runner selection only; policy approval for this ref is already owner-scoped, with no application or security logic changes. > > **Overview** > Updates the **`select-runner`** reusable workflow pin in `.github/workflows/ci.yml` from `de50a08` to **`3415de3`**, adopting the central selector’s **liveness routing** revision from `melodic-software/ci-workflows`. > > No other workflow pins, runner-policy files, or job wiring change—the same `with`/`secrets` contract and `ubuntu-24.04` fallback behavior stay as documented in the workflow header. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit df89fa1. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## What - **Collapse 4 selector jobs into 1**: `ci.yml` ran four identical per-lane `select-runner.yml` preflights (`select-hygiene`, `select-hook-utils-sync`, `select-plugin-gate`, `select-miro-plugin`). They are now one `select-runner` job with the exact same inputs and observer secret; every lane (`hygiene`, `zizmor`, `hook-utils-sync`, `plugin-gate`, `miro-plugin`, `runner-policy`) rewires to it with its existing `!cancelled() && result == 'success'` gate and `outputs.runner || 'ubuntu-24.04'` fallback unchanged. `ci-status` and triggers/concurrency are untouched. - **Pin bump de50a08 → 3415de3** in `ci.yml` and `pr-title.yml`: the liveness selector routes on fleet liveness (any online managed runner keeps the workload self-hosted; GitHub queues on a busy fleet) and drops the rerun-to-hosted branch (melodic-software/ci-workflows#86). The SHA is allowlisted owner-scoped for melodic-software in the synced runner policy (melodic-software/standards#100). - **Docs**: `docs/CI-RUNNER-ROUTING.md` now describes the one-preflight-per-workflow contract instead of one selector per workload. ## Not changed - `zizmor.yml@de50a08` stays pinned: de50a08 is the only zizmor contract SHA approved in the standards-distributed `policy.json`; bumping it would fail the runner-policy gate. ## Verification - `GITHUB_REPOSITORY=melodic-software/claude-code-plugins node .github/standards/runner-policy/runner-policy.mjs --root .` → `Runner policy passed.` - `actionlint` and `markdownlint-cli2` clean on the changed files. Part of melodic-software/github-iac#79, epic #78. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01EPDbXgonTuFwFwdTtHaCmw <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how every CI lane picks runners and bumps the governed selector contract; mis-routing or selector failure would block workloads, though gates and fallbacks are unchanged. > > **Overview** > **Consolidates CI runner selection** so `ci.yml` runs one shared `select-runner` preflight instead of four duplicate per-lane selector jobs; all lanes (`hygiene`, `zizmor`, `hook-utils-sync`, `plugin-gate`, `miro-plugin`, `runner-policy`) still gate on selector success and use the same `outputs.runner || 'ubuntu-24.04'` expression. > > **Bumps** the pinned `select-runner.yml` reusable workflow from `de50a08` to `3415de3` in `ci.yml` and `pr-title.yml` (liveness-based routing; reruns can reuse a prior successful selector result). **`zizmor.yml` stays on `de50a08`** per runner-policy allowlisting. > > **Updates** `docs/CI-RUNNER-ROUTING.md` to document one selector per workflow rather than one per workload. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 600d4b5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>


Part of melodic-software/github-iac#79 (Epic melodic-software/github-iac#78, Phase 1 W1).
What
prefer-self-hostedcandidate test dropsbusy === false; any matching managed-prefix runner withstatus == "online"routes the workflow self-hosted. GitHub natively queues jobs targeting a busy fleet and only fails them after 24 hours queued (routing precedence docs). Hosted fallback now happens only when the fleet is fully offline.run_attempt > 1 → hostedbranch, theRUN_ATTEMPTinput, and the workflow-levelgithub.run_attempt == 1token-mint guard. A re-run reuses the previous attempt's successful selector output (re-run docs), so the branch only converted re-runs into paid hosted work jobs.idle-runner-count→online-runner-count; reasonsidle→online,no-idle-runner→no-online-runner.busyis no longer consumed, so it left inventory validation.render-select-runner-workflow.cjs --checkgreen); README selector contract updated, including one-preflight-job-per-workflow guidance.Not in this PR (sequenced follow-ups)
local-runner-canary.yml/production-ha-proof.ymlstay pinned to the reviewed old selector SHA and stay self-consistent (they assertidlesemantics of that pinned revision). They migrate in their own pin-bump PR once this merges.standardsrunner-policy SHA allowlist bump + consumer-repo fan-out collapse land as separate PRs after merge (allowlist keys on this PR's merge SHA).Verification
node --test .github/scripts/*.test.cjs: 169/169 pass, including new cases: busy online runner routes self-hosted; busy empty-label scale-set runner keeps the inferred route; fully offline fleet routes hosted withno-online-runner.ci-runner-melo-*, fleet offline → hosted, re-run keeps route) runs on a routed private repo after the consumer pin bump, per issue feat: support policy-only Pulumi deploy guard #79.🤖 Generated with Claude Code
https://claude.ai/code/session_01EPDbXgonTuFwFwdTtHaCmw
Note
Medium Risk
Changes adaptive CI runner routing for all
prefer-self-hostedconsumers—busy fleets stay on self-hosted instead of falling back to paid hosted minutes—while removing rerun-forced hosted routing; behavior is well covered by unit tests but rollout affects org-wide workflow execution.Overview
prefer-self-hostednow keys off fleet liveness, not idle capacity. Matching managed-prefix runners withstatus === "online"route self-hosted even whenbusyis true; hosted fallback applies only when no eligible runner is online (no-online-runner), relying on GitHub to queue jobs on a busy fleet.The rerun→hosted shortcut is removed: no
run_attempt > 1preflight branch, noRUN_ATTEMPTenv/input, and the observer-token step no longer gates ongithub.run_attempt == 1. Thererunreason and idle-centric API are gone—idle-runner-count/idle/no-idle-runnerbecomeonline-runner-count/online/no-online-runner, and runner inventory validation no longer requiresbusy.Tests, vendored
select-runner.yml,selector-conformance.yml, and README are updated to match (including busy-online and fully-offline cases, and public-guard expectations without attempt-2rerun).Reviewed by Cursor Bugbot for commit d1e14eb. Bugbot is set up for automated code reviews on this repo. Configure here.