ci(cmux-next): the session label comes only from a placement that judged its queue
Leo, 2026-10-28: "Bro, I thought we already had logic to automatically route stuff. How can we still have pins like this? I hate pins. Like, just pin the set of the stuff that isn't hilariously slow." Rulebook: no job waits on one reserved label; "a CI test fails on a runs-on that can resolve to a single-host label with no fallback".
What was wrong
On 2026-10-29, #24295's daemon tests waited 90 minutes on glaeda-session-std-xcode-26.6, and then did it again. Five session runners were online and busy, and the gui label next to it was starting jobs within a minute. daemon-test, generated-files and both autofix action-exports jobs could still put that label into runs-on as a literal (vars.CI_SESSION_LABEL == '1' && 'glaeda-session-std-xcode-26.6' || ...), so no picker ever looked at its queue. GitHub never moves a queued job. ci-owned-pool-rescue.yml only watches cmux-next runs "refusals-only" when Blacksmith has no free slot, and that day it had none, so nothing moved those jobs.
What changed
- Routes (cmux-next.yml
daemon-testandgenerated-filesruns-on plusCMUX_NEXT_RUNNER, cmux-next-pr-autofix.yml and cmux-next-base-autofix.ymlaction-exports): the session label is never written literally. When the placement gave no route, the last owned leg isglaeda-gui-std-xcode-26.6, and Blacksmith macOS comes after that, as before. The session label still reaches a job, but only through placement outputs (session_runner,free_session_runner,session_lane_runner). - Picker (side_lane_placement.py
role_room(), next_macos_cascade.pySeen.unserved): the janitor's snapshot already writesunserved, the labels with a queued job and none running or started within 10 minutes (starved_labels.py).role_room()now refuses room on such a label unless one of its runners is idle right now. An emptysession_runnerthen sends the jobs togui_runner, and an empty gui route sends them togui_overflow(Blacksmith). Since feat-cmux-next added two keeps tohealthy_role_label()that skiprole_room()(a stalled or dead Blacksmith overflow keeps any online role label, and a known not-full health reading keeps it), the same unserved-and-no-idle-runner check now runs first there too: an unserved queue is not a busy queue that moves.Seen.unservedis read only from a fresh snapshot. - The CI test that should have caught this (tests/test_ci_runs_on_fallback.py): its gui-or-session rule passed these jobs. They had a Blacksmith last leg, were gated on attempt, and sat in a workflow the rescue watches, so the test counted the rescue as their way off. The rescue doesn't move them when Blacksmith is full. The new
test_a_session_label_comes_only_from_a_placement_that_judged_itfails when a runs-on names a session label literally, or reads a session variable without the placement'ssession_lane_runnerverdict. Red on base with 8 findings (all four jobs, literal +vars.CI_SESSION_LABEL); green here. A self-test covers the expression forms. - Tests updated to match: test_ci_session_label_routing.py, test_ci_side_lane_placement.py (autofix route), test_cmux_next_checks_workflow.py (generated-files expected route), test_ci_self_hosted_guard.sh (drops the session-selector exemptions that no longer match anything), test_ci_next_macos_cascade.py (
unservedparsing: single owned keys only).
Other single-label literals checked
glaeda-gui-std-xcode-26.6(cmux-next.yml, autofix): it comes after placement and before a Blacksmith leg, and the rescue watches these workflows. role_label_problems already covers it.glaeda-dev-std-xcode-26.6(FRESH_LABELenv): placement input only, never runs-on.glaeda-side-light-xcode-26.6(cmux-tui-nightly.ymlmacos_runner): a pool label, and its re-run takesmacos_retry_runner(Blacksmith).glaeda-root-std-xcode-26.6(ci-compile-attribution.ymlOWNED): a probe input with its ownOVERFLOW.
None of these is a session label left without a judge, so they stay as they are.
Reds
tests/test_ci_fork_runner_routing.py(SESSION_LANE: ${{ vars.CI_SESSION_LANE_RUNNER }}at cmux-next.yml placement) fails the same way on base. #27548 fixes it.check_no_self_hosted_fleet_runnersin test_ci_self_hosted_guard.sh flags the same five runs-on lines on base.
No new timer, loop or service. Nightly, dev and promote workflows are untouched, and no concurrency or cancel behaviour changed.
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 cmux-next jobs waiting hours on session labels whose runners are online but busy, by ensuring the session label only reaches a job through a placement that judged its queue.
daemon-test,generated-files, and both autofixaction-exportsjobs no longer name the session label literally; their last owned leg is the gui label.role_room()inside_lane_placement.pydeclines labels the janitor finds unserved (a job queued, none running or started within 10 minutes) unless a runner is idle, sosession_runnerfalls back togui_runner;healthy_role_label()applies the same refusal before the stalled-overflow and health-contract keeps bypass it.- The new
test_ci_runs_on_fallback.pytest fails whenever a runs-on names a session label literally or reads a session variable without the placement'ssession_lane_runnerverdict; it fails on base with 8 findings and is green here.
Migration
- No new timers, loops, or services; nightly, dev, and promote workflows are untouched.