ci(nightly-next): canary and train compile take an owned Mac, then Blacksmith; nightly guards stop exempting hosted macOS
Fixes the main half of HQ issue 4720. The ask: "GitHub-hosted macOS is never used, not even as a fallback, and a CI test must fail on any hosted macos-* runs-on. ... Route the #4719 and #4720 jobs through the owned Mac pool with a Blacksmith fallback. Run Linux-capable control and verdict work on Linux. Update the stale nightly-train guard and test that forbids Blacksmith." Leo: "THE GITHUB HOSTED MACS / ARE THE SLOWEST THINGS / THEY PREVIOUSLY WREN'T EVEN UNDER CONSIDERATINO" and "IT WAS ALWAYS FUCKING LAST RESORT" (Blacksmith macOS).
What changed
- nightly-next-canary.yml (main's copy is the one the 15-minute schedule runs). Before, the
canaryMac job ran only onblacksmith-6vcpu-macos-26. Now it takes the owned side label (vars.CI_SIDE_LANE_RUNNER) on attempt 1 while owned pools are on, and Blacksmith macOS 26 from attempt 2 on.pickandverdictalready run on Linux. - nightly-next-train.yml: a new Linux
pick-train-runnerjob runsscripts/ci/nightly_build_runner.py, the picker the nightly-next app build already uses. It picks an owned Mac (vars.NIGHTLY_NEXT_TRAIN_RUNNER, thenvars.CI_SIDE_LANE_RUNNER) only while one of its runners is idle, and otherwise the Blacksmith macOS 26 label whose queue is moving. The compile reads the owned pick only on the pick's own attempt, so a re-run takes Blacksmith. Build mode runs from thenightly-train/*ref the drive pushes from feat-cmux-next, so the copy here is never executed. It changes only so main's owned-pool guard sees the workflow as owned on both branches (the feat-cmux-next PR follows this one). - owned_pool_rescue.py: the canary joins
SIDE_WORKFLOW_PATHS, so a refused canary job is re-run onto Blacksmith. The train goes intoUNRESCUED_WORKFLOW_PATHS, with the reason that its drive re-runs a refused compile itself (feat-cmux-next PR). - Stale guards
tests/test_ci_self_hosted_guard.shno longer stripsmacos-15/macos-26from*nightly*.ymland*notarization*.yml, and the background-lane check no longer skips those files. That exemption came from the retired 2026-10-19 "nightlies never run on Blacksmith" rule.tests/test_ci_nightly_off_blacksmith.pydrops two tests: one refused any non-literal Blacksmith mention, the other refused every shared runner variable. In their place it refuses a single-host label (cmux22,glaeda-runner-*), as feat-cmux-next's copy does. Its benchmark-mini rule matches feat-cmux-next's.test_ci_no_github_hosted_macos.py's train check now asserts thatvars.NIGHTLY_NEXT_TRAIN_RUNNERreaches only the picker'sOWNED_LABELS, which offers only ownedglaeda-*labels.
Why the hosted-macOS guard missed these
tests/test_ci_no_github_hosted_macos.py(landed in #26085, 2026-10-27 02:54 PDT) catches all three forms named in the HQ issues. Run on the pre-fix files, it reports:- the seed's
{"pool": "hosted", "runner": "macos-26"}JSON; - the canary's bare
runs-on: macos-26; - the train's
${{ vars.NIGHTLY_NEXT_TRAIN_RUNNER || 'macos-26' }}.
- the seed's
- Those three lines were already gone from both branches (#26085 and #25887) before the research snapshot the issues cite.
- What let them in before was the older shell guard:
check_no_github_hosted_runnersandcheck_no_self_hosted_fleet_runnersstrippedmacos-15|26from every nightly file.check_background_macos_laneskipped nightly files outright.- The runner-selection scan reads only
runs-on:,os:and list lines, so a hosted label inside an env JSON string (the seed'sSEED_POOLS, read throughmatrix.runner) was never seen.
- The Python guard reads every non-comment line and does not exempt nightly files. This PR removes the shell guard's nightly exemptions, so neither guard has a nightly loophole now.
Before / after
- Canary Mac job: before,
blacksmith-6vcpu-macos-26on every run. After, owned side label on attempt 1, Blacksmith on attempt 2+. - Train compile: before,
vars.NIGHTLY_NEXT_TRAIN_RUNNER(unset), elseblacksmith-6vcpu-macos-26. After, an idle owned Mac, else the moving Blacksmith label. - Repository variables: none changed.
Tests
These ran on a fast-checks box (run 40636515009, exit 0):
test_ci_no_github_hosted_macos.py,test_ci_nightly_off_blacksmith.py,test_ci_owned_pool_rescue.py,test_ci_owned_pool_sources.py(as main, against feat-cmux-next's tip)test_ci_queue_janitor.py,test_ci_workflow_run_sources.py,test_runner_label_policy.py- the nightly-next canary and train tests,
test_nightly_promotion_admission.py test_ci_self_hosted_guard.sh
Risk: low.
- The canary is advisory and holds no secrets. A refused or stuck owned job moves to Blacksmith by the side-lane rescue and the queue janitor.
- The train is dry-run only (
NIGHTLY_NEXT_TRAINunset), and its compile runs from feat-cmux-next. - Open PRs that touch the same files: #26040 changes the canary's feed-origin line, not the runs-on line, so they don't conflict. #26614 changes
nightly_build_runner.py, which this PR only calls, so it composes with this change.
Agent session: agent session
🤖 Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the main half of HQ issue 4720: GitHub-hosted macOS is never used, even as a fallback, and a CI test fails on any hosted macos-* runs-on.
- The canary's Mac job takes the owned side label on attempt 1 and Blacksmith macOS 26 from attempt 2 on; fork PRs go to Blacksmith immediately.
- The train compile gets a Linux picker that returns an idle owned Mac, else the moving Blacksmith label.
- The owned-pool rescue treats the canary as a side lane (re-run onto Blacksmith) and the train as rescued by its own drive.
- The self-hosted guard no longer exempts nightly and notarization files from the hosted-macOS and background-lane checks, and the stale Blacksmith-variable ban becomes a single-host-label ban.
- The train workflow's copy on main is never executed (the drive builds from the
nightly-train/*ref it pushes); it changes so the owned-pool guard sees both branches as owned.
Summary by CodeRabbit
- Chores
- Nightly macOS builds now select from available runner pools, with fallback routing when an owned runner is unavailable or a retry is needed.
- Nightly workflows avoid routing builds to specific single-host runners and exclude GitHub-hosted Macs. These updates affect build infrastructure and do not change app functionality.