Skip to content

ci: every runner selection reads its size's declared pool (no single-size pins, 1/2) - #32457

Merged
teamleaderleo merged 1 commit into
feat-cmux-nextfrom
lane/no-pins-workflows
Nov 2, 2026
Merged

teamleaderleo merged 1 commit into
feat-cmux-nextfrom
lane/no-pins-workflows

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Leo-ask: ask-973a915bea

Leo: "Where the f*** are the generalized solution to all these fing problems that these incessant fing pins?"

Part 1 of 2: the workflows. Part 2 adds the guard that keeps them this way.

Size: about 2,080 changed lines. 932 are one-for-one workflow line swaps (the same transform on 169 workflow files, proven by the round trip below); the rest are the tests that assert those exact lines and must change with them, so it is not split further.

Prior art: #32393 (per-size pools on main) and #32417 (pool copy check) landed the pools these reads use. #27114 (open) moves queued jobs off a stalled label only when their runs-on reads a pool, so this makes it apply to almost every job. #27440 and #27448 (open) change some macOS sizes on lines this touches; whichever lands second rebases.

Why

A runs-on that ends in a literal blacksmith-<N>vcpu-* label can never move: when that size stops starting jobs (2026-10-28, nightly.yml's 4 vCPU fallback), every run waits until someone edits the YAML. One declared runner set fixes that. Each size has its own pool in main's .github/runners.json (#32393), and scripts/ci/runner_pools.py writes each pool's live member to vars.RUNNER_POOLS every 10 minutes. A job that reads its measured size's pool, fromJSON(vars.RUNNER_POOLS).linux_8, follows the pool when it fails over, with no YAML edit. The live variable has had every per-size key since 15:28Z today.

What

Each literal single-size label in a runner selection (runs-on, matrix pools, *RUNNER* env mirrors, reusable-workflow runner inputs) becomes its size's pool:

Literal Pool
blacksmith-2/4/8/16/32vcpu-ubuntu-2404 linux_2 / linux_4 / linux_8 / linux_16 / linux_32
blacksmith-4vcpu-ubuntu-2404-arm linux_arm64
blacksmith-6vcpu-macos-15 / -macos-26 / -macos-latest macos_15 / macos_26 / macos_latest
blacksmith-12vcpu-macos-26 macos_26_large
blacksmith-4vcpu-windows-2025 windows

Kept as they were:

  • Forks. A fork's pull_request run gets no repository variables. Where a fork branch already exists (head.repo.full_name != github.repository && '<label>'), it keeps its literal and comes before any pool read. Where a workflow a fork can trigger had none, each pool read is wrapped as (<fork pull request> && '<old label>' || fromJSON(vars.RUNNER_POOLS).<pool>), so a fork resolves to exactly the label it resolved to before. Inside a fork branch's own operand, labels stay literal.
  • Owner checks, membership lists and comparisons (== '<label>', contains(fromJSON('[...]'), ...)) are checks, not routes, and are unchanged.
  • The trusted-token selector keeps its literal. contains(fromJSON('[...]'), vars.CI_TRUSTED_RUNNER) && vars.CI_TRUSTED_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' (pull_request_target, comment events, CLA, about 27 workflows) is runner trust policy: a pool read there would let whoever writes RUNNER_POOLS route the trusted token. It stays byte-for-byte as on base.
  • cmux-next-guards.yml signing-runner-variables keeps its literal: it checks the runner variables, so its own route must not read them.
  • Signing and release jobs keep their routes, byte for byte. A job that holds signing or release material (scripts/ci/runner_routing_table.py's sensitive: Apple/signing/notary secrets, codesign, notarytool, the signing keychain, release uploads) is left as it is. 42 such jobs in 15 workflows keep their routes. A pool read there would let whoever writes RUNNER_POOLS choose the machine that holds the keys. That is runner trust policy, and the release lane's own check (tests/test_ci_release_sdk_lane.sh) holds the release signer's literal fallback whole. Their pickers (pick-sign-runner and the like) hold no secret and do read pools. The light-Linux hosted signer lines in tests/test_ci_self_hosted_guard.sh are unchanged.
  • No cancellation and no concurrency changes. No cancel-in-progress or concurrency group is touched.

Proof the routing is unchanged: replacing every pool read with the literal it replaced (and unwrapping each fork wrap) turns all 466 changed workflow lines back into their base lines exactly. Every per-size pool's first member is that literal.

Tests that assert Blacksmith is absent (the nightly Blacksmith-only ratchet, the signer never-16/32 checks, the owned-only nightly checks) now also refuse the matching pool read, since every per-size pool is Blacksmith; BLACKSMITH_LINUX_ONLY_DEBT is unchanged from base.

Performance

Each pool's first member is the label the job named before, so routing is unchanged until a size stalls. After that, the job moves to the pool's next member instead of waiting.

Metric Before After Delta Harness
Blacksmith minutes per day same jobs, same sizes same jobs, same sizes 0 while every size starts .github/runners.json pools: first member = previous literal
Jobs that stay queued on a stalled size every job pinned to it none (the pool moves within ~10 min) n/a scripts/ci/runner_pools.py reconcile

Testing

All 147 tests that read workflows or runner routing (tests/ files naming runs-on, RUNNER_POOLS, blacksmith- or the touched workflows) ran on this head, plus tests/test_ci_no_single_size_pins.py:

  • 142 pass.
  • 4 fail exactly as on base 9fde804c, the same tests in the same way: test_check_ghostty_zig_workflows (bashlex not installed), test_ci_cloud_probe_rescue, test_ci_fleet_rule_lints, test_ci_merge_main (needs git 2.46).
  • test_ci_fast_checks_failover timed out once under the parallel run and passes on its own, as it does on base.
  • The round trip (each pool read -> its old literal) reads back 466 of 466 changed workflow lines to base, the 2 others being the ci-guards step rename in part 2.

Observed / Not run

Ran all the above locally with python3 and bash. Not run: the workflows themselves on GitHub (routing is unchanged until a pool fails over, by the round trip).

Changelog

none

Agent session: agent session

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 83d4bbf: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr32457 -f ref=lane/no-pins-workflows -f platform=macos -f backend_mode=local

Passes: CI passes on 83d4bbf0ac.

CI passes on 83d4bbf0ac (run 41493127915 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

Shipped in:

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/41497919334 at f6b340f failed: cmux-next crash ends the app, cmux-next daemon tests.
Those jobs last passed at ac1dcde. This pull request is one of 5 merged since: #32423, #32457, #32494, #32495, #32490.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

@github-actions github-actions Bot added risk:high Risk tier (docs/ci/risk-tiers.md) and removed risk:normal Risk tier (docs/ci/risk-tiers.md) labels Nov 2, 2026
@blacksmith-sh

blacksmith-sh Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
test_full_suite_runs_still_require_the_suite/
test_full_suite_runs_still_require_the_suite
View Logs
test_linux_failure_still_blocks_ci_status_after_macos_succeeds/
test_linux_failure_still_blocks_ci_status_after_macos_succeeds
View Logs
test_linux_preflight_skips_when_macos_route_is_false (main.LinuxGuardRoutingTests.t
est_linux_preflight_skips_when_macos_route_is_false)/
test_linux_preflight_skips_when_macos_route_is_false (main.LinuxGuardRoutingTests.t
est_linux_preflight_skips_when_macos_route_is_false)
View Logs
test_platform_workflow_results_gate_ci_status/
test_platform_workflow_results_gate_ci_status
View Logs
the short nightly decide gate must use the pooled Linux route with a Blacksmith fallbac
k/
the short nightly decide gate must use the pooled Linux route with a Blacksmith fallbac
k
View Logs
zsh is not installed on this CI runner/zsh is not installed on this CI runner View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

A literal single-size Blacksmith label in runs-on, a matrix pool, a *RUNNER* env mirror or a reusable-workflow
runner input becomes fromJSON(vars.RUNNER_POOLS).<pool> (main's .github/runners.json pools, written every 10
minutes by scripts/ci/runner_pools.py), so a size that stops starting jobs is left by the pool instead of by a
YAML edit. Each pool's first member is the literal it replaces: routing is unchanged until a size stalls.

Forks keep their literal: an existing fork branch stays ahead of any read, and a read a fork could reach is
wrapped as (<fork pull request> && '<old label>' || <pool read>). Comparisons, membership lists, the owner branch
and the CI_TRUSTED_RUNNER selector (runner trust policy) keep their literals. Tests that assert exact lines now
expect the pool reads, and tests that assert Blacksmith is absent also refuse the pool read.

Leo-ask: ask-973a915bea
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-04cad32e0d917ebc8a49
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

This is a note for the lane adding swift-test-fleet / daemon-test-fleet jobs and Linux twin branches to cmux-next.yml.

This PR rewrites each literal Blacksmith label in cmux-next.yml runs-on to its size pool, for example fromJSON(vars.RUNNER_POOLS).linux_16. Fork branches keep their literal ahead of the read. #32458 adds a guard that fails on a literal single-size label in a runner selection.

Please write new branches in pool form. Whichever PR lands second rebases. This PR does not touch next_macos_cascade.py.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, sweep, via the review router; full diff from merge base 9fde804 to this head): BLOCK at bb52cf4

[P1] [blocking] tests/test_ci_repo_variable_defaults.py:45 - The pool fallback matcher does not recognize parenthesized pool routes such as ... || (fork-label || fromJSON(...).linux_4), so this guard reports 49 false errors on the changed workflows and exits nonzero; the paid-overflow matcher has the same issue at line 367 - parse balanced parenthesized fallback expressions (including fork-wrapped pool branches) before declaring a missing default.

@teamleaderleo
teamleaderleo merged commit bbb2b89 into feat-cmux-next Nov 2, 2026
286 of 310 checks passed
@teamleaderleo
teamleaderleo deleted the lane/no-pins-workflows branch November 2, 2026 17:58
teamleaderleo added a commit that referenced this pull request Nov 2, 2026
…rsal nightly workflow) (#32529)

- Validate nightly push throttle (test_ci_warm_sources): #32513 moved
  "Build and sign the release notes" into release-notes-build-sign.sh, and
  the warm-sources detector reads a script named *build*.sh as a compile.
  The step compiles nothing, so the script is renamed release-notes-sign.sh.
- Validate universal nightly workflow (test_nightly_universal_build.sh):
  #32457 turned literal runner labels into declared pool reads
  (fromJSON(vars.RUNNER_POOLS).<pool>, whose first member is the old
  literal) but left three exact-expression checks expecting the literals:
  the decide gate, refresh-compilation-cache and
  build-nightly-ghostty-cli-helper. They now expect the pool reads #32457
  wrote; each still matches the job's whole expression.

Base-red-job: cmux-next guards / nightly-guards / workflow-guard-tests / release-notary


Agent-Session: https://cmux.com/deeplink/agent?id=aid-f724bb1ff9295c18fee7

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 2, 2026
…32653)

#32457 moved the iOS kit picker's fallback to fromJSON(vars.RUNNER_POOLS).linux_8, but the
fixture variable held only `linux`, so the picker's runs-on resolved to null and four
"iOS kit picker fallback" cases failed on feat-cmux-next. The fixture now holds each pool
declared in .github/runners.json at its first member (what runner_pools.py keeps it at),
and the iOS kit cases test the pool reads: the eight vCPU pool, a moved pool, the bounded
LINUX_RUNNER sizes and the saved-overflow sixteen vCPU pool. The unset and empty variable
cases are gone: RUNNER_POOLS is required and an unset one fails the run at fromJSON().


Agent-Session: https://cmux.com/deeplink/agent?id=aid-73244be6791fcec67af9

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Nov 3, 2026
feat-cmux-next's #32457 made nearly every runner selection read
vars.RUNNER_POOLS, and the guard counted each as able to take an owned Mac,
so CI fast guards failed on main and on every pull request, and the macOS
admission gate then refused them. runner_pools.py rejects any pool member
that isn't an ephemeral Blacksmith label, so these reads can't reach one.

fast-checks.yml was watched only for that read, so its entry goes too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 25fb044)
teamleaderleo added a commit that referenced this pull request Nov 4, 2026
#35630)

#30657 added scripts/ci/nightly_next_control_runner.py and
nightly_next_control_rescue.py as a helper-only prerequisite for #30509,
which closed in favour of the runner-pool design (#32457). No workflow on
feat-cmux-next or main calls either; only their own tests, a pool test and
the test registry named them. The tick already picks its runner from
RUNNER_POOLS.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-f08996319b14c7281027

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 4, 2026
…size

feat-cmux-next's runner guards refuse a literal single-size label (#32457) and
one with no overflow failover (#26439), and the old linux_2 route was reachable
outside manaflow-ai. The job now takes the owner fork branch, then the overflow
pool linux_16 or linux_8, the same pools backend.yml's credentialed jobs end in.
It no longer reads vars.LINUX_RUNNER, so that variable cannot move it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-8b9c8964a6aea0b7e2bd
teamleaderleo added a commit that referenced this pull request Nov 4, 2026
… production journey names its runner (#35717)

* ci: the backend secret-route guard covers the three Cloud journey jobs

journey-preview, journey-staging and journey-production (#35103) read secrets
and the cmux-next-cloud-journey environment, so they belong in
BACKEND_SECRET_JOBS; without them the guard's credentialed-set check fails and
their routes go unchecked. Their routes already have the bounded pool-read
shape. cloud-journey-prod.yml's job holds the production account, so it names
blacksmith-4vcpu-ubuntu-2404 literally, as on main (#35303).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-8b9c8964a6aea0b7e2bd

* ci: the production journey reads the Blacksmith pools, not a literal size

feat-cmux-next's runner guards refuse a literal single-size label (#32457) and
one with no overflow failover (#26439), and the old linux_2 route was reachable
outside manaflow-ai. The job now takes the owner fork branch, then the overflow
pool linux_16 or linux_8, the same pools backend.yml's credentialed jobs end in.
It no longer reads vars.LINUX_RUNNER, so that variable cannot move it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-8b9c8964a6aea0b7e2bd

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 83d4bbf0 Deployed Nov 2, 2026 by teamleaderleo via cmux-tui tree preflight #102146
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant