ci(cmux-next): a doc-only or daemon-only PR skips the Mac fast tier and swift test
The Mac queue was full. At 12:43Z glaeda-gui-std had 11/11 busy and 57 queued, glaeda-side-std had 44 queued, blacksmith-6vcpu-macos-26 was at its cap with 47 queued (oldest 120 min), and blacksmith-12vcpu-macos-26 had started nothing in 15 h. Every Mac job a PR doesn't need makes that wait longer for the PRs that do.
The rule (Leo, 2026-10-27): "skip compiles that add no signal before adding machines ... a PR that changes no Swift does not Mac-compile".
Why #28326 still got both Mac jobs
At 5d76d56f, #28326 changes only plans/cmux-next/sidebar-owner.md, yet run 41055761832 queued cmux-next fast tier and cmux-next swift test. The cause is in ci-target-graph.py. It records a test target's reads as the repository paths its string literals name, and it cuts an interpolated literal at its first \(. ActionSurfaceParityTests.planURL and LinkExportTests.planURL build "plans/cmux-next/\(name)". So the graph lists all of plans/cmux-next/ as a read of CmuxNextActionsTests and CmuxNextAppTests. Every design note there (411 files) then routed to swift test and the generated tier.
Those helpers only ever open action-surfaces.json, actions.md and links.json, and all three are generated outputs.
What changes
-
Docs under a directory read. A doc (
.md,.htmlor image) under a directory read counts as read only when one of these holds:- a string literal in the reading target's Swift sources (comment lines excluded) names it;
- a literal names the directory itself, as a test that lists the directory would;
- a generator writes it (
scripts/cmux-next/generated_outputs.py).
Otherwise it is a doc no test reads, and on its own it runs no Mac tier, as any other doc already does. Non-doc files there (JSON, text) keep the directory read, because a computed name may open them. A doc that no directory read covers, or one under
Packages/, routes exactly as before. -
The Mac fast tier follows its own inputs. It builds only the CmuxNext package and runs a few of its suites (
fast-tier.json'sswiftentries). So a new route output,fast_tier, is true only for these changes:- a package target or a local package it uses;
- a file a test reads;
- a generated input;
- an input of every tier;
- the Release-compile inputs;
- the tier's own registry and runner.
A cmux-tui-only or app-host-only PR keeps its daemon and scheme tiers but no longer starts the Mac fast tier.
fast-tier-cargostill runs on Linux. Thefast-tierjob'sif:and the placement'sCASCADE_RUNSboth read the new output. A failed route (no output) and a feat-cmux-next push run the fast tier as before. A skipped job reports as skipped, as the Mac fast tier already does on a web-only PR. -
The routing replay compares
fast_tiertoo. A base router without the output ran the fast tier with any Mac tier. A dropped fast tier is explained only by its own reason. A narrowed Swift target set is explained only by the doc reason. A widened set is never explained.
Measured on the queue
From the 12:43Z fleet dashboard, 111 cmux-next Mac jobs were queued on 71 PR branches. I routed each PR's current files through the base router and through this one:
| jobs | PRs | |
|---|---|---|
queued cmux-next Mac jobs on PRs | 111 (54 swift test, 53 fast tier, 3 Release compile, 1 daemon) | 71 |
| that this route skips | 18 (15 fast tier, 3 swift test) | 16 |
| their summed queue time | 554 min (oldest 101 min) |
#28326 now routes to no Mac tier at all. The other 15 PRs are cmux-tui Rust, optchat or nightly-script changes that still run their daemon and scheme tiers. Caveat: some of those runs were at an older head than the PR's current files.
Replay of the last 60 merged feat-cmux-next PRs, base router against this one: none unexplained, 24 the same. The other 36 change as follows:
- 33 drop only the Mac fast tier, because they are cmux-tui-, optchat- or script-only.
- #28187 drops generated and swift test, because its only Mac input was
plans/cmux-next/setup-DESIGN.md. - #27912 and #26786 narrow their Swift targets: CmuxNextActionsTests goes, because only a plans doc reached it.
A doc that no directory read covers, or one under Packages/ (it may be a bundled resource), routes as before.
Tests (red on the base router, green here)
tests/test_cmux_next_route.pygains two classes.UnreadDocs: #28326's file alone runs no Mac tier, and beside Swift it routes like the Swift alone.actions.md(a generated export the tests name) keeps its tiers, and non-doc files keep the directory read. Literal fixtures check four cases: a comment mention, a name passed to a helper, a listed directory and an explicit path. Unreadable sources keep the read.FastTierRoute: package, schema, registry and toolchain inputs run the fast tier; cmux-tui, optchat and App/ changes don't; push,full-ciand vendored pins run it.
tests/test_cmux_next_checks_workflow.py(MacFastTierFollowsTheRoute) evaluates the jobif:with the route's outputs. A daemon-only PR starts no Mac fast tier but still runsfast-tier-cargo, and the placement cascade list readsfast_tier.tests/test_cmux_next_route_replay.py(FastTierAndDocs) covers the replay changes above.- Also green on the lane: test_cmux_next_checks_workflow (165), test_ci_next_macos_cascade, test_ci_side_lane_placement, test_ci_runs_on_fallback, test_car_guards, test_ci_compile_test_plan, test_ci_cmux_next_compile_status, test_job_input_fingerprint, test_settings_stamp_not_committed, test_cmux_next_generated_fold and test_cmux_next_tip_swift_suite.
Risk: low. This is routing only, with no permission, secret or runner change. The worst case is a doc that a test opens through a computed name missing its swift test on a PR. The tip Swift suite and the base push still cover it.
Agent session: agent session
š¤ Generated with Claude Code