diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 6aea213e5451..ab439486f0ff 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -2999,8 +2999,8 @@ jobs: # than falling back when it cannot find one. The macos-26 image has no SDK 15. # It is the one pull-request macOS job MACOS_RUNNER_PR must not move. # - # The owned Macs: a same-repository pull request run whose picker placed - # ' swift-package ' in pr_owned_jobs takes the pool's side label + # The owned Macs: a same-repository pull request run (or main's full-suite + # dispatch) whose picker placed ' swift-package ' in pr_owned_jobs takes the pool's side label # (pr_side_runner), else the owned label, on attempt 1 only, with the # lane's Xcode. Any retry takes the macOS 15 Blacksmith default. # The picker places it only when this run builds no helper @@ -3045,12 +3045,12 @@ jobs: # capacity ledger fits the light class; that mini checks out the commit # itself and streams the log and exit code back. PACKAGE_TESTS_VIA_STEP # below restates the runs-on branch for the steps. - runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-15' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-15' || github.event_name == 'pull_request' && !(inputs.full_suite == 'true' && inputs.release_build == 'true') && vars.CI_SWIFT_PACKAGE_TESTS_STEP_GATEWAY || github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && contains(inputs.pr_owned_jobs, ' swift-package ') && ((github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') && (inputs.pr_side_runner || inputs.pr_runner)) || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_DUAL_XCODE || 'blacksmith-6vcpu-macos-15') }} + runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-15' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-15' || github.event_name == 'pull_request' && !(inputs.full_suite == 'true' && inputs.release_build == 'true') && vars.CI_SWIFT_PACKAGE_TESTS_STEP_GATEWAY || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' swift-package ') && (inputs.pr_side_runner || inputs.pr_runner) || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_DUAL_XCODE || 'blacksmith-6vcpu-macos-15') }} timeout-minutes: 60 env: # The owned label's Xcode (the lane pin) exactly when runs-on took it; # the pool picker's Wiring tests keep the two conditions equal. - CMUX_CI_XCODE_APP: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && contains(inputs.pr_owned_jobs, ' swift-package ') && ((github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') && (inputs.pr_side_runner || inputs.pr_runner)) && (inputs.pr_xcode_app || vars.CMUX_CI_XCODE_APP_PR) || vars.CMUX_CI_XCODE_APP_MACOS_15 }} + CMUX_CI_XCODE_APP: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' swift-package ') && (inputs.pr_side_runner || inputs.pr_runner) && (inputs.pr_xcode_app || vars.CMUX_CI_XCODE_APP_PR) || vars.CMUX_CI_XCODE_APP_MACOS_15 }} CMUX_CI_HELPER_XCODE_APP: ${{ vars.CMUX_CI_HELPER_XCODE_APP_MACOS_15 }} CMUX_CI_REQUIRED_MACOS_SDK_MAJOR: "26" PACKAGE_TESTS_VIA_STEP: ${{ github.repository_owner == 'manaflow-ai' && github.event.pull_request.head.repo.full_name == github.repository && vars.CI_SWIFT_PACKAGE_TESTS_STEP_GATEWAY != '' && github.event_name == 'pull_request' && !(inputs.full_suite == 'true' && inputs.release_build == 'true') && '1' || '0' }} @@ -3821,18 +3821,28 @@ jobs: # compiles into the same artifact shape as nightly and stable releases. # Release builds need enough disk for a universal Release build plus the # restored SwiftPM cache, which the macOS 26 image already carries. The - # variable names that image, not this lane. - runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-26' || vars.MACOS_RUNNER_26 || 'blacksmith-6vcpu-macos-26') }} + # variable names that image, not this lane. A same-repository pull request + # or main's full-suite dispatch takes the owned side label when the picker + # placed ' release-build ' in pr_owned_jobs (pr_runner_pool.RELEASE_BUILD_JOB) + # on attempt 1 or a manual re-run of a pull request (attempt 1 only on + # main, whose re-runs the janitor charges no owned machine), with the + # lane's Xcode; glaeda's hook classes the job isolated. A bot re-run + # (ci-owned-pool-rescue.yml) takes the macOS 26 variable. The picker never + # gives it the light pool's side runners, + # and swift-package-tests (the only lane ci.yml hands the light label + # through pr_side_runner) never shares a run with it. + runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' release-build ') && (inputs.pr_side_runner || inputs.pr_runner) || vars.MACOS_RUNNER_26 || 'blacksmith-6vcpu-macos-26') }} timeout-minutes: 60 permissions: actions: read attestations: read contents: read env: - CMUX_CI_XCODE_APP: ${{ vars.CMUX_CI_XCODE_APP_MACOS_26 }} + # The owned label's Xcode exactly when runs-on took it, as swift-package-tests. + CMUX_CI_XCODE_APP: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' release-build ') && (inputs.pr_side_runner || inputs.pr_runner) && (inputs.pr_xcode_app || vars.CMUX_CI_XCODE_APP_PR) || vars.CMUX_CI_XCODE_APP_MACOS_26 }} CMUX_CI_REQUIRED_MACOS_SDK_MAJOR: "26" CMUX_SKIP_ZIG_BUILD: "1" - CMUX_PRODUCT_RUNNER: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-26' || vars.MACOS_RUNNER_26 || 'blacksmith-6vcpu-macos-26') }} + CMUX_PRODUCT_RUNNER: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' release-build ') && (inputs.pr_side_runner || inputs.pr_runner) || vars.MACOS_RUNNER_26 || 'blacksmith-6vcpu-macos-26') }} CMUX_RELEASE_SOURCE_REVISION: ${{ github.event.pull_request.head.sha || github.sha }} steps: - name: Clear stale git locks (self-hosted reused workspace) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e8b221a5e35..da7003c33062 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1190,7 +1190,7 @@ jobs: name: Claude wrapper regressions needs: [changes, static-preflight] if: ${{ !cancelled() && needs.changes.result == 'success' && needs.static-preflight.result == 'success' && (needs.changes.outputs.claude_wrapper == 'true' || (needs.changes.outputs.macos == 'true' && needs.changes.outputs.full_suite == 'true')) }} - runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (startsWith(needs.changes.outputs.macos_pr_runner, 'blacksmith-') && needs.changes.outputs.macos_pr_runner || 'blacksmith-6vcpu-macos-15') || github.event_name == 'pull_request' && (github.run_attempt > 1 && github.triggering_actor == 'github-actions[bot]' || !contains(needs.changes.outputs.macos_pr_owned_jobs, ' claude-wrapper ')) && needs.changes.outputs.macos_pr_retry_runner || github.event_name == 'pull_request' && ((contains(needs.changes.outputs.macos_pr_light_side_jobs, ' claude-wrapper ') && needs.changes.outputs.macos_pr_light_side_runner || needs.changes.outputs.macos_pr_side_runner) || needs.changes.outputs.macos_pr_runner || vars.MACOS_RUNNER_PR || 'blacksmith-6vcpu-macos-15') || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15') }} + runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (startsWith(needs.changes.outputs.macos_pr_runner, 'blacksmith-') && needs.changes.outputs.macos_pr_runner || 'blacksmith-6vcpu-macos-15') || github.event_name == 'pull_request' && (github.run_attempt > 1 && github.triggering_actor == 'github-actions[bot]' || !contains(needs.changes.outputs.macos_pr_owned_jobs, ' claude-wrapper ')) && needs.changes.outputs.macos_pr_retry_runner || github.event_name == 'pull_request' && ((contains(needs.changes.outputs.macos_pr_light_side_jobs, ' claude-wrapper ') && needs.changes.outputs.macos_pr_light_side_runner || needs.changes.outputs.macos_pr_side_runner) || needs.changes.outputs.macos_pr_runner || vars.MACOS_RUNNER_PR || 'blacksmith-6vcpu-macos-15') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1 && contains(needs.changes.outputs.macos_pr_owned_jobs, ' claude-wrapper ') && (needs.changes.outputs.macos_pr_side_runner || needs.changes.outputs.macos_pr_runner) || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15') }} # Normally 1-2 minutes, but it spawns hundreds of short bash, perl and node # processes, so a mini saturated by a compile beside it stretches it: # 120-527 s at a mean load of 36-100 on 14 cores (glaeda-cmux-jobs.jsonl, diff --git a/.github/workflows/remote-daemon.yml b/.github/workflows/remote-daemon.yml index 36a43a1c7cb5..8512de9be7d0 100644 --- a/.github/workflows/remote-daemon.yml +++ b/.github/workflows/remote-daemon.yml @@ -114,7 +114,7 @@ jobs: # contended macOS 26 pool. A direct push or dispatch of this workflow (not # ci.yml's call) is a side lane: the minis on attempt 1, Blacksmith on # any retry. - runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (startsWith(inputs.pr_runner, 'blacksmith-') && inputs.pr_runner || 'blacksmith-6vcpu-macos-15') || github.event_name == 'pull_request' && ((github.run_attempt > 1 && github.triggering_actor == 'github-actions[bot]' || !contains(inputs.pr_owned_jobs, ' remote-daemon ')) && inputs.pr_retry_runner || inputs.pr_side_runner || inputs.pr_runner || vars.MACOS_RUNNER_PR || 'blacksmith-6vcpu-macos-15') || contains(github.workflow_ref, '/.github/workflows/remote-daemon.yml@') && vars.CI_PR_POOL_OWNED == '1' && contains(fromJSON('["pull_request","push","schedule","workflow_dispatch"]'), github.event_name) && (github.run_attempt == 1 && (vars.CI_LIGHT_LANE_RUNNER || vars.CI_SIDE_LANE_RUNNER)) || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15' }} + runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (startsWith(inputs.pr_runner, 'blacksmith-') && inputs.pr_runner || 'blacksmith-6vcpu-macos-15') || github.event_name == 'pull_request' && ((github.run_attempt > 1 && github.triggering_actor == 'github-actions[bot]' || !contains(inputs.pr_owned_jobs, ' remote-daemon ')) && inputs.pr_retry_runner || inputs.pr_side_runner || inputs.pr_runner || vars.MACOS_RUNNER_PR || 'blacksmith-6vcpu-macos-15') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1 && contains(inputs.pr_owned_jobs, ' remote-daemon ') && (inputs.pr_side_runner || inputs.pr_runner) || contains(github.workflow_ref, '/.github/workflows/remote-daemon.yml@') && vars.CI_PR_POOL_OWNED == '1' && contains(fromJSON('["pull_request","push","schedule","workflow_dispatch"]'), github.event_name) && (github.run_attempt == 1 && (vars.CI_LIGHT_LANE_RUNNER || vars.CI_SIDE_LANE_RUNNER)) || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15' }} timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/docs/ci-runners.md b/docs/ci-runners.md index bfa06490c3c2..c3f80eeef2a1 100644 --- a/docs/ci-runners.md +++ b/docs/ci-runners.md @@ -754,7 +754,7 @@ and the retired self-hosted fleet failed `codesign` with | Jobs | Route | Why | | --- | --- | --- | | `ci-macos.yml` compile admission, app-host shards, `tests-build-and-lag`, `cli-product-tests` | owned via `pr_runner_pool.py` (root label), pull requests and main's full-suite dispatch | canonical-root jobs | -| `ci.yml` `claude-wrapper`, `remote-daemon.yml` macOS tests | owned side lane via the picker (the side label) | light | +| `ci.yml` `claude-wrapper`, `remote-daemon.yml` macOS tests | owned side lane via the picker (the side label), pull requests and main's full-suite dispatch | light | | `ci-macos.yml` `swift-package-tests` | owned side lane via the picker (the side label) when the run builds no Release helper; else Blacksmith macOS 15 | the helper needs an SDK 15 Xcode | | the light side lanes above | `CI_LIGHT_LANE_RUNNER` on attempt 1, `CI_SIDE_LANE_RUNNER` on attempt 2, of a pull request, push, schedule or dispatch | light | | `test-e2e.yml` (and `dispatch-focused-test.py`) | owned via `e2e_runner_pool.py`; UI runs with `CI_E2E_OWNED_UI=1` | root jobs; Blacksmith when no root runner is free | @@ -762,7 +762,7 @@ and the retired self-hosted fleet failed `codesign` with | `app-host-test-rerun.yml` `rerun` | `CI_SIDE_LANE_RUNNER` for macOS 26 products, attempt 1 only; macOS 15 products on Blacksmith macOS 15 | gui; it takes the product's root itself (`glaeda-canonical-root take`) | | `cmux-tui.yml` macOS `lint`, `test`, `cdp-browser-smoke` | `CI_SIDE_LANE_RUNNER`, attempt 1 only | isolated (glaeda classes them by workflow and id) | | `cmux-tui.yml` release-path dogfood `build` (`cmux-tui-build-package.yml`) | Blacksmith macOS 15 | the release packaging build, shared with the release and nightly callers; its matrix is planned once, so a re-run could not leave the minis | -| `ci-macos.yml` `release-build` | `MACOS_RUNNER_26` | could move; needs a picker key and a glaeda class | +| `ci-macos.yml` `release-build` | owned side lane via the picker (`release-build`, the picked std pool's side label, never the light pool), pull requests (attempt 1 or a manual re-run) and main's full-suite dispatch (attempt 1); else `MACOS_RUNNER_26` | isolated: an unsigned universal Release into its own DerivedData, Xcode 26.6 | | `reload-build.yml` `build` | `CI_SIDE_LANE_RUNNER` for a macOS build when the runner input is `auto` or `blacksmith-6vcpu-macos-26`, attempt 1 only (iOS builds take Blacksmith); any other label as given | isolated: a Debug build into the workspace | | low-volume GUI dispatches: `test-macos-suite`, `tmux-corpus`, `perf-activation`, command palette benchmarks | Blacksmith or the caller's runner input | 0 to 1 runs a week; they drive the app in the runner's own session, which a mini's runner lacks (E2E and the rerun use its console session) | | `iroh-release-gate` version skew | Blacksmith macOS 15 | pins the macOS 15 pool's Xcode 26.3 | diff --git a/scripts/ci/pr_runner_pool.py b/scripts/ci/pr_runner_pool.py index 6f9d603265db..0a6c72ac823e 100644 --- a/scripts/ci/pr_runner_pool.py +++ b/scripts/ci/pr_runner_pool.py @@ -279,9 +279,9 @@ wait up to the queue rounds and the bound (owned_room()). CI_OWNED_MAIN_RESERVE (0 when unset) holds that many machines and root runners back for pull requests; with a reserve it takes an owned pool only whole, and only while its peak is free now (no queue -allowance). Its side lanes (the Claude wrapper and -remote daemon) route only for pull requests, so they are not in its plan. -Main's CI concurrency group holds one run at a time, so main holds at most +allowance). Its side lanes (the Claude wrapper, the remote daemon and the +universal Release build) are in its plan like a pull request's, and read the +pick through the same inputs. Main's CI concurrency group holds one run at a time, so main holds at most one run's machines. ci-owned-pool-rescue.yml watches it like a pull request. Anything uncertain keeps today's route: an event other than pull_request or @@ -398,7 +398,10 @@ def host_fault_retry(run_attempt: int, triggering_actor: str | None) -> bool: # suite with release_build false, which then peaks at all three side lanes # beside admission and its nine follow-on jobs. MAX_RUN_JOBS counts all three; # the replay charge leaves out the package lane, which a compile-only run -# carries only on a package change. +# carries only on a package change. release-build (RELEASE_BUILD_JOB) is the +# package lane's alternative: it runs only on a full suite with release_build, +# exactly when swift-package-tests builds the SDK 15 helper on Blacksmith, so a +# run still has at most three side lanes. APP_HOST_SHARDS = 7 SIDE_LANES = 3 MAX_RUN_JOBS = SIDE_LANES + APP_HOST_SHARDS + 2 @@ -547,7 +550,8 @@ def light_side_lanes(plan: "RunJobs", runners: Sequence[Mapping[str, Any]], owne pr_xcode_app: str | None) -> tuple[str, tuple[str, ...]]: """The light pool's side label and the side lanes of `plan` its idle side runners take now, one per runner. - ("", ()) when none is idle, and always while CI_OWNED_POOL_SLOTS gives + release-build (RELEASE_BUILD_JOB), a universal Release compile, is never + one of them. ("", ()) when none is idle, and always while CI_OWNED_POOL_SLOTS gives the light pool no machines beyond its root runners (side_runner()'s rule), so removing that count turns it off. """ @@ -555,7 +559,8 @@ def light_side_lanes(plan: "RunJobs", runners: Sequence[Mapping[str, Any]], owne label = side_label(light) if not plan.side or not label or owned_slots.get(light, 0) <= owned_slots.get(root_label(light), 0): return "", () - lanes = plan.side[:max(0, live_owned_free(runners, [label])[label])] + # release-build stays with the picked pool: ci-macos.yml gives it only side_runner. + lanes = tuple(key for key in plan.side if key != RELEASE_BUILD_JOB)[:max(0, live_owned_free(runners, [label])[label])] return (label, lanes) if lanes else ("", ()) @@ -675,13 +680,16 @@ def run_plan(*, macos: str | None, full_suite: str | None, unit_suite: str | Non that does not know) counts one shard, as before. swift-package-tests is a side lane only when package_lane_owned() says it may take the pool; `swift_packages` None (a caller that does not pass it) leaves it out. + release-build is a side lane on a full suite with `release_build` true; + None leaves it out. """ full = flag(macos) and flag(full_suite) side = tuple(key for key, on in (("claude-wrapper", flag(claude_wrapper) or full), ("remote-daemon", flag(remote_daemon)), (SWIFT_PACKAGE_JOB, package_lane_owned( full=full, full_suite=full_suite, swift_packages=swift_packages, - release_build=release_build))) if on) + release_build=release_build)), + (RELEASE_BUILD_JOB, full and flag(release_build))) if on) if not (flag(macos) or flag(cli)): return RunJobs(False, (), side) unit = flag(macos) and flag(unit_suite) and not flag(unit_in_admission) @@ -703,6 +711,11 @@ def run_plan(*, macos: str | None, full_suite: str | None, unit_suite: str | Non # Blacksmith macOS 15 image carries (the minis have Xcode 26.6 alone), so only # a run without that helper build places it on an owned pool. SWIFT_PACKAGE_JOB = "swift-package" +# ci-macos.yml release-build: the unsigned universal Release app nightly signs, +# into its own workspace DerivedData with the lane's Xcode 26.6 (glaeda's hook +# classes it isolated: no GUI, product, canonical root or secrets). It runs +# after admission and swift-package-tests on its own machine. +RELEASE_BUILD_JOB = "release-build" def package_lane_owned(*, full: bool, full_suite: str | None, swift_packages: str | None, @@ -725,12 +738,14 @@ def run_jobs(**routing: str | None) -> int: # Owned placement priority: the heavy compile, then GUI jobs (the longest # Blacksmith queues), then light jobs. GUI jobs need the mini's console # session; CI_PR_POOL_OWNED_GUI=0 keeps them off. -LIGHT_JOBS = ("cli-product", "remote-daemon", "claude-wrapper", SWIFT_PACKAGE_JOB) +# release-build is not light (a 15-minute universal compile), but it follows +# cli-product: it is the side lane that saves the most Blacksmith time. +LIGHT_JOBS = ("cli-product", RELEASE_BUILD_JOB, "remote-daemon", "claude-wrapper", SWIFT_PACKAGE_JOB) # glaeda's canonical-root jobs: admission and every job after it (RunJobs.after: # the shards, tests-build-and-lag, cli-product-tests). The side lanes are not. ROOT_JOBS = "admission, shards, lag, cli-product" # The side lanes (RunJobs.side): light, no canonical root; they take side_runner() on a pool with a root count. -SIDE_LANE_JOBS = ("claude-wrapper", "remote-daemon", SWIFT_PACKAGE_JOB) +SIDE_LANE_JOBS = ("claude-wrapper", "remote-daemon", SWIFT_PACKAGE_JOB, RELEASE_BUILD_JOB) def gui_job(key: str) -> bool: @@ -2211,10 +2226,6 @@ def count_routed(since: str) -> int: cli=env.get("RUN_CLI"), remote_daemon=env.get("RUN_REMOTE_DAEMON"), unit_selectors=env.get("RUN_UNIT_SELECTORS"), swift_packages=env.get("RUN_SWIFT_PACKAGES"), release_build=env.get("RUN_RELEASE_BUILD")) - if on_main: - # The side lanes read the pick only on a pull request (ci.yml's - # claude-wrapper, remote-daemon.yml); main's keep their own route. - plan = dataclasses.replace(plan, side=()) # What an owned pool must have free for the whole run: its owned-eligible # jobs at their peak. gui = (env.get("POOL_OWNED_GUI") or "").strip() != "0" @@ -2296,6 +2307,9 @@ def count_routed(since: str) -> int: # run takes retry_runner. The marker's jobs are the owned machines held. owned_slots = slots(env.get("OWNED_SLOTS"), pr_xcode_app) gui_label_out = gui_runner(choice, owned_slots) + if choice.runner.startswith(f"glaeda-{LIGHT_CLASS}-"): + # The light pool's own pick places no universal Release compile; it keeps MACOS_RUNNER_26. + plan = dataclasses.replace(plan, side=tuple(key for key in plan.side if key != RELEASE_BUILD_JOB)) owned_jobs, held = (place(plan, choice.owned_budget, gui, choice.root_budget if choice.root_runner else None, bool(gui_label_out)) if persistent(choice.runner) else ((), plan.peak)) diff --git a/tests/test_ci_change_areas.py b/tests/test_ci_change_areas.py index 07de1a1ad4a2..045802758787 100755 --- a/tests/test_ci_change_areas.py +++ b/tests/test_ci_change_areas.py @@ -6247,10 +6247,10 @@ def test_macos_jobs_use_lane_specific_xcode_pin_vars() -> None: package_block = workflow_job_block("swift-package-tests", MACOS_WORKFLOW) assert "vars.MACOS_RUNNER_PR" not in package_block assert ( - "CMUX_CI_XCODE_APP: ${{ github.event_name == 'pull_request' && " - "github.event.pull_request.head.repo.full_name == github.repository && " - "contains(inputs.pr_owned_jobs, ' swift-package ') && " - "((github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') && (inputs.pr_side_runner || inputs.pr_runner)) && " + "CMUX_CI_XCODE_APP: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && " + "(github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || " + "github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && " + "contains(inputs.pr_owned_jobs, ' swift-package ') && (inputs.pr_side_runner || inputs.pr_runner) && " "(inputs.pr_xcode_app || vars.CMUX_CI_XCODE_APP_PR) || vars.CMUX_CI_XCODE_APP_MACOS_15 }}" ) in package_block assert ( @@ -6260,7 +6260,10 @@ def test_macos_jobs_use_lane_specific_xcode_pin_vars() -> None: assert 'CMUX_CI_REQUIRED_MACOS_SDK_MAJOR: "26"' in package_block release_block = workflow_job_block("release-build", MACOS_WORKFLOW) - assert "CMUX_CI_XCODE_APP: ${{ vars.CMUX_CI_XCODE_APP_MACOS_26 }}" in release_block + assert ( + "CMUX_CI_XCODE_APP: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' release-build ') && (inputs.pr_side_runner || inputs.pr_runner) " + "&& (inputs.pr_xcode_app || vars.CMUX_CI_XCODE_APP_PR) || vars.CMUX_CI_XCODE_APP_MACOS_26 }}" + ) in release_block assert 'CMUX_CI_REQUIRED_MACOS_SDK_MAJOR: "26"' in release_block diff --git a/tests/test_ci_pr_runner_pool.py b/tests/test_ci_pr_runner_pool.py index d2d510d07254..8d58ae3abf37 100644 --- a/tests/test_ci_pr_runner_pool.py +++ b/tests/test_ci_pr_runner_pool.py @@ -2064,6 +2064,25 @@ def test_side_lanes_take_the_light_side_runners_when_enough_are_idle(self): # A retry attempt keeps its own route. self.assertEqual(self.outputs(std + idle, slots=slots, attempt="2", extra=lanes)["light_side_jobs"], "") + def test_release_build_stays_with_the_picked_pool(self): + # The universal Release compile never takes the light side runners ahead of the pick; the lanes + # before it still do, and it takes the picked pool's side label. + light_side, light_root = pool.side_label(LIGHT), pool.root_label(LIGHT) + slots = '{"std": 40, "root-std": 10, "light": 4, "root-light": 2}' + lanes = {"RUN_FULL_SUITE": "true", "RUN_RELEASE_BUILD": "true", "RUN_CLAUDE_WRAPPER": "true"} + std = [live_runner(1, MINI, ROOT_MINI), live_runner(2, MINI, SIDE_MINI), live_runner(3, MINI, SIDE_MINI)] + idle = [live_runner(21, LIGHT, light_side), live_runner(22, LIGHT, light_side), live_runner(23, LIGHT, light_root)] + values = self.outputs(std + idle, slots=slots, extra=lanes) + self.assertEqual((values["runner"], values["side_runner"], values["light_side_runner"], + values["light_side_jobs"]), (MINI, SIDE_MINI, light_side, " claude-wrapper ")) + self.assertIn(" release-build ", values["owned_jobs"]) + self.assertEqual(pool.light_side_lanes(pool.RunJobs(False, (), ("release-build",)), idle, + {LIGHT: 4, light_root: 2}, PR_XCODE), ("", ())) + # The light pool's own pick leaves it to MACOS_RUNNER_26 too. + values = self.outputs(idle, slots=slots, extra={**lanes, "POOL_ORDER": LIGHT}) + self.assertEqual(values["runner"], LIGHT) + self.assertNotIn(" release-build ", values["owned_jobs"]) + def test_light_side_lanes_on_the_light_pick_count_in_its_peak(self): # The janitor takes the side lanes off the marker's peak for the root share, so the peak holds them. light_side, light_root = pool.side_label(LIGHT), pool.root_label(LIGHT) @@ -2404,6 +2423,13 @@ def test_a_rerun_of_failed_shards_leaves_the_owned_pool(self): self.assertIn("github.event_name == 'pull_request' && " "(github.run_attempt > 1 && github.triggering_actor == 'github-actions[bot]' || !contains(needs.changes.outputs.macos_pr_owned_jobs, " "' claude-wrapper ')) && needs.changes.outputs.macos_pr_retry_runner", wrapper) + # Main's dispatch takes the side label only where the picker placed the wrapper. + # Attempt 1 only: a re-run of main's dispatch takes no owned machine (the janitor charges none). + self.assertIn("|| github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && " + "github.run_attempt == 1 && " + "contains(needs.changes.outputs.macos_pr_owned_jobs, ' claude-wrapper ') && " + "(needs.changes.outputs.macos_pr_side_runner || needs.changes.outputs.macos_pr_runner) " + "|| vars.CI_PAID_MACOS_OVERFLOW == '1'", wrapper) def test_callers_pass_the_choice(self): jobs = self.workflow("ci.yml")["jobs"] @@ -2422,10 +2448,12 @@ def test_callers_pass_the_choice(self): # Each side lane: the light side label when the picker put that lane there, else the side label. self.assertEqual(jobs["remote-daemon"]["with"]["pr_side_runner"], "${{ contains(needs.changes.outputs.macos_pr_light_side_jobs, ' remote-daemon ') && needs.changes.outputs.macos_pr_light_side_runner || needs.changes.outputs.macos_pr_side_runner }}") self.assertEqual(jobs["macos"]["with"]["pr_side_runner"], "${{ contains(needs.changes.outputs.macos_pr_light_side_jobs, ' swift-package ') && needs.changes.outputs.macos_pr_light_side_runner || needs.changes.outputs.macos_pr_side_runner }}") - # In ci-macos.yml only swift-package-tests reads it; its root jobs never do. + # In ci-macos.yml only the side lanes read it (swift-package-tests and + # release-build); its root jobs never do. release-build never shares a + # run with an owned swift-package-tests, so the light label never reaches it. macos_jobs = self.workflow("ci-macos.yml")["jobs"] readers = sorted(name for name, job in macos_jobs.items() if "pr_side_runner" in yaml.safe_dump(job)) - self.assertEqual(readers, ["swift-package-tests"]) + self.assertEqual(readers, ["release-build", "swift-package-tests"]) self.assertEqual(self.workflow("ci.yml")["jobs"]["changes"]["outputs"]["macos_pr_side_runner"], "${{ steps.macos-pool.outputs.side_runner }}") self.assertEqual(jobs["macos"]["with"]["pr_admission_runner"], @@ -2538,9 +2566,10 @@ def test_package_tests_take_an_owned_mac_only_where_the_picker_placed_them(self) # (MACOS_RUNNER_PR) or the retry pool; only the owned label, on the # attempts that read it, when owned_jobs names ' swift-package '. job = self.workflow("ci-macos.yml")["jobs"]["swift-package-tests"] - owned = ("github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && " - "contains(inputs.pr_owned_jobs, ' swift-package ') && " - "((github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') && (inputs.pr_side_runner || inputs.pr_runner))") + owned = ("(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && " + "(github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || " + "github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && " + "contains(inputs.pr_owned_jobs, ' swift-package ') && (inputs.pr_side_runner || inputs.pr_runner)") self.assertEqual(job["runs-on"], ( "${{ github.repository_owner != 'manaflow-ai' && 'macos-15' || (github.event_name == 'pull_request' && " "github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-15' || " @@ -2566,6 +2595,47 @@ def test_package_tests_take_an_owned_mac_only_where_the_picker_placed_them(self) self.assertIn("inputs.full_suite == 'true' && inputs.release_build == 'true'", step.get("if", ""), step.get("name")) + def test_release_build_takes_an_owned_mac_only_where_the_picker_placed_them(self): + # The universal Release build: the side label when owned_jobs names + # ' release-build ' (same repository or main's dispatch), else the + # macOS 26 variable, and its product-contract mirror says the same. + job = self.workflow("ci-macos.yml")["jobs"]["release-build"] + owned = ("(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && " + "(github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || " + "github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && " + "contains(inputs.pr_owned_jobs, ' release-build ') && (inputs.pr_side_runner || inputs.pr_runner)") + expected = ("${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && " + "github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-26' || " + f"{owned} || vars.MACOS_RUNNER_26 || 'blacksmith-6vcpu-macos-26') }}}}") + self.assertEqual(job["runs-on"], expected) + self.assertEqual(job["env"]["CMUX_PRODUCT_RUNNER"], expected) + # The Xcode follows the same condition: the lane pin on the owned label. + self.assertEqual(job["env"]["CMUX_CI_XCODE_APP"], + f"${{{{ {owned} && (inputs.pr_xcode_app || vars.CMUX_CI_XCODE_APP_PR) " + "|| vars.CMUX_CI_XCODE_APP_MACOS_26 }}") + self.assertEqual(pool.RELEASE_BUILD_JOB, "release-build") + + def test_release_build_is_a_side_lane_of_a_full_suite_with_release_build(self): + full = dict(macos="true", full_suite="true", unit_suite="false", unit_in_admission="false", + claude_wrapper="true", cli="true", remote_daemon="false") + plan = pool.run_plan(**full, swift_packages="true", release_build="true") + # The helper build keeps swift-package-tests on Blacksmith; release-build takes its place. + self.assertEqual(plan.side, ("claude-wrapper", "release-build")) + self.assertNotIn("release-build", pool.run_plan(**full, swift_packages="true", release_build="false").side) + self.assertNotIn("release-build", pool.run_plan(**full, swift_packages="true").side) + self.assertNotIn("release-build", pool.run_plan(**{**full, "full_suite": "false"}, + release_build="true").side) + # Still at most three side lanes, so the most machines a run holds is unchanged. + self.assertLessEqual(pool.run_plan(**{**full, "remote_daemon": "true"}, swift_packages="true", + release_build="true").peak, pool.MAX_RUN_JOBS) + keys, held = pool.place(plan, plan.peak) + self.assertIn("release-build", keys) + self.assertEqual(held, plan.peak) + # Behind the root jobs and cli-product, ahead of the light side lanes. + order = sorted(("claude-wrapper", "remote-daemon", "release-build", "cli-product", "lag"), key=pool.priority) + self.assertEqual(order, ["lag", "cli-product", "release-build", "remote-daemon", "claude-wrapper"]) + self.assertIn("release-build", pool.SIDE_LANE_JOBS) + def test_the_picker_reads_the_package_lane_routing(self): env = next(step for step in self.workflow("ci.yml")["jobs"]["changes"]["steps"] if step.get("id") == "macos-pool")["env"] @@ -2698,7 +2768,7 @@ def test_anything_else_keeps_its_route(self): choice = self.main_choice(self.snap(), **kwargs) self.assertEqual((choice.runner, choice.root_runner), ("", ""), kwargs) - def test_main_routes_the_full_suite_without_its_side_lanes(self): + def test_main_routes_the_full_suite_with_its_side_lanes(self): with tempfile.TemporaryDirectory() as tmp: snapshot = Path(tmp, "snap.json") fresh = self.snap() @@ -2715,10 +2785,11 @@ def test_main_routes_the_full_suite_without_its_side_lanes(self): with unittest.mock.patch("sys.stdout", io.StringIO()): self.assertEqual(pool.main(["--snapshot", str(snapshot)], env), 0) values = dict(line.split("=", 1) for line in out.read_text().splitlines()) + # The nine root jobs at their peak, plus the Claude wrapper and the remote daemon beside them. self.assertEqual((values["runner"], values["persistent"], values["root_runner"], values["jobs"]), - (MINI, "true", ROOT_MINI, "9")) + (MINI, "true", ROOT_MINI, "11")) self.assertEqual(values["owned_jobs"], " admission " + " ".join(f"shard-{index}" for index in range(1, 8)) - + " lag cli-product ") + + " lag cli-product remote-daemon claude-wrapper ") self.assertTrue(values["retry_runner"].startswith("blacksmith-")) # A dispatch on another branch writes the default route. env.update(GITHUB_REF="refs/heads/topic") diff --git a/tests/test_ci_release_sdk_lane.sh b/tests/test_ci_release_sdk_lane.sh index cfccc0156c28..fd0e1248df9b 100755 --- a/tests/test_ci_release_sdk_lane.sh +++ b/tests/test_ci_release_sdk_lane.sh @@ -44,8 +44,8 @@ require_job_contains \ require_job_contains \ "$CI_FILE" \ "release-build" \ - 'runs-on: ${{ github.repository_owner != '\''manaflow-ai'\'' && '\''macos-26'\'' || (github.event_name == '\''pull_request'\'' && github.event.pull_request.head.repo.full_name != github.repository && '\''blacksmith-6vcpu-macos-26'\'' || vars.MACOS_RUNNER_26 || '\''blacksmith-6vcpu-macos-26'\'') }}' \ - "CI release-build must use GitHub-hosted macOS on forks and the macOS 26 runner variable upstream" + 'runs-on: ${{ github.repository_owner != '\''manaflow-ai'\'' && '\''macos-26'\'' || (github.event_name == '\''pull_request'\'' && github.event.pull_request.head.repo.full_name != github.repository && '\''blacksmith-6vcpu-macos-26'\'' || (github.event_name == '\''pull_request'\'' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != '\''github-actions[bot]'\'') || github.event_name == '\''workflow_dispatch'\'' && github.ref == '\''refs/heads/main'\'' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, '\'' release-build '\'') && (inputs.pr_side_runner || inputs.pr_runner) || vars.MACOS_RUNNER_26 || '\''blacksmith-6vcpu-macos-26'\'') }}' \ + "CI release-build must use GitHub-hosted macOS on forks, the picked owned side lane where placed, and the macOS 26 runner variable upstream" for workflow in "$CI_FILE" "$RELEASE_FILE"; do if ! grep -Fq "CMUX_SKIP_ZIG_BUILD=1 xcodebuild" "$workflow"; then @@ -74,10 +74,11 @@ swift_package_section="$(job_section "$CI_FILE" "swift-package-tests")" # job builds the Release Ghostty CLI helper against an SDK 15 Xcode, which only # the macos-15 image carries, so it must not follow MACOS_RUNNER_PR onto # whatever pool that lane points at. The one exception is a same-repository -# pull request whose picker placed ' swift-package ' on an owned Mac, which the +# pull request (or main's full-suite dispatch) whose picker placed +# ' swift-package ' on an owned Mac, which the # picker does only for a run that builds no helper (package_lane_owned()), # and the opt-in build-fleet gateway (hq#794), also only without the helper. -expected_runs_on='runs-on: ${{ github.repository_owner != '\''manaflow-ai'\'' && '\''macos-15'\'' || (github.event_name == '\''pull_request'\'' && github.event.pull_request.head.repo.full_name != github.repository && '\''blacksmith-6vcpu-macos-15'\'' || github.event_name == '\''pull_request'\'' && !(inputs.full_suite == '\''true'\'' && inputs.release_build == '\''true'\'') && vars.CI_SWIFT_PACKAGE_TESTS_STEP_GATEWAY || github.event_name == '\''pull_request'\'' && github.event.pull_request.head.repo.full_name == github.repository && contains(inputs.pr_owned_jobs, '\'' swift-package '\'') && ((github.run_attempt == 1 || github.triggering_actor != '\''github-actions[bot]'\'') && (inputs.pr_side_runner || inputs.pr_runner)) || vars.CI_PAID_MACOS_OVERFLOW == '\''1'\'' && vars.MACOS_RUNNER_DUAL_XCODE || '\''blacksmith-6vcpu-macos-15'\'') }}' +expected_runs_on='runs-on: ${{ github.repository_owner != '\''manaflow-ai'\'' && '\''macos-15'\'' || (github.event_name == '\''pull_request'\'' && github.event.pull_request.head.repo.full_name != github.repository && '\''blacksmith-6vcpu-macos-15'\'' || github.event_name == '\''pull_request'\'' && !(inputs.full_suite == '\''true'\'' && inputs.release_build == '\''true'\'') && vars.CI_SWIFT_PACKAGE_TESTS_STEP_GATEWAY || (github.event_name == '\''pull_request'\'' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != '\''github-actions[bot]'\'') || github.event_name == '\''workflow_dispatch'\'' && github.ref == '\''refs/heads/main'\'' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, '\'' swift-package '\'') && (inputs.pr_side_runner || inputs.pr_runner) || vars.CI_PAID_MACOS_OVERFLOW == '\''1'\'' && vars.MACOS_RUNNER_DUAL_XCODE || '\''blacksmith-6vcpu-macos-15'\'') }}' if [[ "$swift_package_section" != *"$expected_runs_on"* ]]; then echo "FAIL: CI swift-package-tests must use the dual-Xcode runner lane on every event" >&2 exit 1 diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index fa99357fb3ea..c849bcc0aaac 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -94,7 +94,7 @@ check_release_build_runner_disk_capacity() { # paid-overflow gate appearing here, which does not belong: MACOS_RUNNER_26 # is the free macOS 26 pool and is read ungated everywhere. See # docs/ci-runners.md for why the gate must not grow to cover it. - if ! awk -v release_runner="runs-on: \${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-26' || vars.MACOS_RUNNER_26 || 'blacksmith-6vcpu-macos-26') }}" ' + if ! awk -v release_runner="runs-on: \${{ github.repository_owner != 'manaflow-ai' && 'macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-26' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' release-build ') && (inputs.pr_side_runner || inputs.pr_runner) || vars.MACOS_RUNNER_26 || 'blacksmith-6vcpu-macos-26') }}" ' /^ release-build:/ { in_job=1; next } in_job && /^ [^[:space:]#][^:]*:[[:space:]]*(#.*)?$/ { in_job=0 } in_job && index($0, release_runner) { saw_release_runner=1 } @@ -300,7 +300,7 @@ check_release_helper_artifact_from_package_lane() { # label, only when the picker placed ' swift-package ' in pr_owned_jobs, # which it does only for a run that skips the SDK 15 helper steps (pr_runner_pool.package_lane_owned()). # The opt-in build-fleet gateway (hq#794) likewise takes only a run without the helper. - if ! awk -v dual_runner="runs-on: \${{ github.repository_owner != 'manaflow-ai' && 'macos-15' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-15' || github.event_name == 'pull_request' && !(inputs.full_suite == 'true' && inputs.release_build == 'true') && vars.CI_SWIFT_PACKAGE_TESTS_STEP_GATEWAY || github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && contains(inputs.pr_owned_jobs, ' swift-package ') && ((github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') && (inputs.pr_side_runner || inputs.pr_runner)) || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_DUAL_XCODE || 'blacksmith-6vcpu-macos-15') }}" ' + if ! awk -v dual_runner="runs-on: \${{ github.repository_owner != 'manaflow-ai' && 'macos-15' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'blacksmith-6vcpu-macos-15' || github.event_name == 'pull_request' && !(inputs.full_suite == 'true' && inputs.release_build == 'true') && vars.CI_SWIFT_PACKAGE_TESTS_STEP_GATEWAY || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (github.run_attempt == 1 || github.triggering_actor != 'github-actions[bot]') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1) && contains(inputs.pr_owned_jobs, ' swift-package ') && (inputs.pr_side_runner || inputs.pr_runner) || vars.CI_PAID_MACOS_OVERFLOW == '1' && vars.MACOS_RUNNER_DUAL_XCODE || 'blacksmith-6vcpu-macos-15') }}" ' /^ swift-package-tests:/ { in_job=1; next } in_job && /^ [^[:space:]#][^:]*:[[:space:]]*(#.*)?$/ { in_job=0 } @@ -1363,6 +1363,11 @@ GUARDED = ( # place on the owned pool: the Blacksmith pool the picker named for it. "github.event_name == 'pull_request' && (github.run_attempt > 1 && github.triggering_actor == 'github-actions[bot]' || !contains(needs.changes.outputs.macos_pr_owned_jobs," " ' claude-wrapper ')) && needs.changes.outputs.macos_pr_retry_runner", + # The full-suite dispatch on main (code already on main, which + # pr_runner_pool.py places like a pull request): only the job it placed. + "github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1" + " && contains(needs.changes.outputs.macos_pr_owned_jobs, ' claude-wrapper ') && (needs.changes.outputs.macos_pr_side_runner" + " || needs.changes.outputs.macos_pr_runner)", )