Skip to content

ci: run unsigned iOS jobs on owned minis with counted simulator capacity - #14389

Merged
teamleaderleo merged 2 commits into
mainfrom
ci-ios-owned-pool
Sep 25, 2026
Merged

teamleaderleo merged 2 commits into
mainfrom
ci-ios-owned-pool

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Lets the unsigned iOS CI jobs run on the owned Mac mini pool. They pick it with the same owned-pool picker that pull request and E2E runs use (#14205, #14225), and fall back to Blacksmith. Routing is off until CI_IOS_OWNED=1 is set. That variable is new, and this PR does not set it.

What changes

New scripts/ci/ios_runner_pool.py. It applies the E2E rule (e2e_runner_pool.decide, which calls pr_runner_pool.decide) and accepts only an owned pool. If the picker would pick a Blacksmith pool instead (for example the 12vcpu overflow), iOS stays on its own variable, so MACOS_RUNNER_IOS keeps its current meaning. An auto run goes to the minis only when all of these hold:

  • CI_PR_POOL_OWNED=1 and CI_IOS_OWNED=1
  • the default resolves to blacksmith-6vcpu-macos-26, as for E2E
  • no ios_version input, no App Store upload, no seed_cache (it runs in the ci-cache-writer environment with the R2 write keys), and not called from a release
  • the owned pool has 2 machines free
  • simulator capacity: the glaeda-ios-sim entry of CI_OWNED_POOL_SLOTS (for example {"glaeda-ios-sim": 2}) leaves one simulator mini free for each requested device family. Without that entry the lane never routes on its own. A swift_package run holds one machine and no simulator, so it skips this check.

Every other case keeps the default. No job is sent to wait in an owned queue.

Labels. Jobs that need the iOS runtime (ios-simulator-build, ios-simulator, screenshots) request [glaeda-std-xcode-<pin>, glaeda-ios-sim]. They never request the pool label alone. mobile-core-package runs host SwiftPM tests and needs no simulator, so it takes the pool label alone. glaeda classifies these jobs itself (isolated or simulator token), so they never take the root label. Both labels come from the picker's JSON output. No glaeda- label appears in any workflow runs-on: line, so the self-hosted guard still holds.

Capacity accounting.

  • pr_runner_pool gains CAPABILITY_LABELS and capability_slots(). A glaeda-ios-sim entry in CI_OWNED_POOL_SLOTS is not reported as a slot problem, and slots() still returns only pools.
  • queue_janitor counts queued and running jobs that carry the capability label.
  • queue_janitor also reads a per-run capability marker (macos-pool-persistent-<run>-<attempt>-<sim_jobs>-glaeda-ios-sim), so a run's simulator jobs count as taken before they exist.
  • Every in-flight test-ios or ios-screenshots run created since the snapshot costs 2 simulators.
  • The build job carries the label too, so the count errs toward Blacksmith.

test-ios.yml

  • New runner job (actions: read). The three macOS jobs read fromJSON(run_attempt > 1 && retry_runs_on || runs_on).
  • The fork branch, the tart-ios Tart identity checks and the GitHub-hosted check all still work. They now read the runner job's label.
  • runner input gains owned, which forces the owned pool for a proof run without reading the queue. It is an explicit request, so it fails the runner job with an error, never a fallback, unless CI_PR_POOL_OWNED=1 (otherwise the rescue never watches the run and a queued job would wait for good) and CI_OWNED_POOL_SLOTS gives glaeda-ios-sim at least one mini. It also refuses what auto refuses. CI_IOS_OWNED is not required, so the proof run can come first.
  • Jobs on a glaeda- label set CMUX_CI_XCODE_APP to CMUX_CI_XCODE_APP_PR, as test-e2e.yml does, so the Xcode matches the pool label; a Blacksmith re-run keeps it.
  • runner is added to the ios-tests gate's needs.
  • A simulator this run created is removed on reused machines too.

ios-screenshots.yml: screenshots job.

Rescue. ci-owned-pool-rescue.yml / owned_pool_rescue.py now watch test-ios and ios-screenshots dispatches exactly like E2E. A job left queued or refused (a second simulator job on a mini) gets its failed jobs re-run on retry_runs_on, which is Blacksmith.

Kept on Blacksmith

  • ios-streamed-validate (validate). Not routed. It binds ports 3000 and 13000, runs rm -rf /tmp/cmuxpg, uses shared ~/Library DerivedData, puts CLAUDE_CODE_OAUTH_TOKEN into the GUI session with launchctl setenv, and flips the global dark mode. It also writes agent credentials under $HOME and launches the app through launchd. The new secrets cleanup is general hygiene only; it is not a reason to route this job.
  • Secrets hygiene. ios-streamed-validate and iroh-release-gate now write $HOME/.secrets/cmuxterm-dev.env under umask 077 and delete it in an if: always() step. The file cannot move to $RUNNER_TEMP because the Mac app's DebugDogfoodCredentialResolver, scripts/lib/dev-secrets.sh and web/scripts/load-dev-env.sh all read that fixed path.
  • Signing workflows. ios-testflight, ios-app-store and ios-appstore-upload hold the distribution certificate, the provisioning profiles and the ASC keys. They are untouched.
  • Runs that need another runtime or write credentials. Any ios_version run, any App Store upload, any seed_cache run, and the release call of ios-screenshots.

Next: nightly/beta

The TestFlight uploads (plain cmux, INTERNAL, BETA) move to a dedicated release mini whose label takes only signing jobs, with Blacksmith as the fallback. That's a follow-up PR. Decision and host choice: manaflow-ai/cmuxterm-hq#627.

Rollout

  1. The glaeda-ios-sim label ships (glaeda#1218) on minis with the iOS simulator role and an iOS 26.x runtime.
  2. CI_PR_POOL_OWNED=1 (already set), so the rescue watches the run.
  3. Add "glaeda-ios-sim": <sim minis> to CI_OWNED_POOL_SLOTS.
  4. Proof run: gh workflow run test-ios.yml --repo manaflow-ai/cmux -f runner=owned. Do this only after step 1 is live.
  5. Set CI_IOS_OWNED=1.

Tests

  • tests/test_ci_pr_runner_pool.py gains IOSRouting and IOSWiring. They cover capacity counting, package-only runs needing no simulator, the label shape, the switches, the blockers (including seed_cache), forced owned and its refusals without CI_PR_POOL_OWNED or simulator slots, the Xcode pin, the janitor's capability counting and markers, the workflow wiring, signing and streamed-validate staying off the fleet, and a guard that every $HOME/.secrets write has a later always() removal.
  • tests/test_ci_owned_pool_rescue.py covers iOS dispatch targets and a job stuck waiting on glaeda-ios-sim.
  • test_ci_self_hosted_guard.sh, test_ios_workflow_dispatch_ref.py and test_ci_workflow_run_sources.py are updated.
  • Local results: every test above passes; also test_ci_queue_janitor, test_ci_reusable_workflow_permissions, test_run_e2e, test_runner_label_policy, test_ci_actionlint_covers_every_workflow, and actionlint 1.7.7 on all workflows. tests/test_ios_simulator_build_once.py fails the same way on unmodified main locally (the macOS /var symlink), and this PR does not touch it.

🤖 Generated with Claude Code


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


Summary by cubic

Lets the unsigned iOS CI jobs (test-ios.yml, ios-screenshots.yml) run on the owned Mac mini pool, picked by the same pool picker that E2E and pull request runs use, with simulator capacity counted so minis never oversubscribe. Routing is off until the new CI_IOS_OWNED variable is set to 1; nothing changes by default. Activation requires the minis to carry the glaeda-ios-sim label and a matching entry in CI_OWNED_POOL_SLOTS.

Routing

  • test-ios.yml and ios-screenshots.yml gain a runner job running the new scripts/ci/ios_runner_pool.py; all macOS jobs read its choice and re-runs fall back to Blacksmith.
  • An auto run routes to a mini only when CI_IOS_OWNED and CI_PR_POOL_OWNED are 1, the pool has two machines free, and the glaeda-ios-sim slot entry leaves a simulator mini free per requested device family; otherwise MACOS_RUNNER_IOS keeps its meaning. ios_version, seed_cache, App Store upload, and release calls stay on Blacksmith. A swift_package run takes one mini and needs no simulator, so it skips the capacity check.
  • Simulator jobs request the pool label together with glaeda-ios-sim; mobile-core-package takes the pool label alone. runner: owned forces the fleet for a proof run, failing unless CI_PR_POOL_OWNED is 1 and glaeda-ios-sim has minis (the rescue would not watch it otherwise).
  • Jobs on a glaeda- label take CMUX_CI_XCODE_APP_PR; the queue janitor and a per-run marker count simulator capacity, and the rescue workflow now watches iOS dispatches and re-runs stuck or refused jobs on Blacksmith.

Secrets hygiene

  • ios-streamed-validate and iroh-release-gate write $HOME/.secrets/cmuxterm-dev.env under umask 077 and remove it in an always() step.

Written for commit d08b033. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • iOS simulator tests and screenshot runs can now be routed to supported Mac runner pools, with automatic selection based on availability and capacity.
    • Manual screenshot runs offer runner choices, including automatic and owned-pool options.
    • Owned-pool rescue now covers iOS simulator and screenshot workflow runs.
  • Bug Fixes

    • Improved cleanup of temporary credentials and simulator resources after workflow runs.
    • Runner selection and retry routing account for simulator capacity and ongoing iOS jobs.

test-ios.yml (mobile-core-package, ios-simulator-build, ios-simulator)
and ios-screenshots.yml (screenshots) get a `runner` job that runs the new
scripts/ci/ios_runner_pool.py. It applies the E2E pool rule
(e2e_runner_pool.decide over pull request CI's picker) and only ever
accepts an owned pool: otherwise the run keeps MACOS_RUNNER_TESTS /
MACOS_RUNNER_IOS as before.

An auto run takes the minis only when CI_PR_POOL_OWNED and the new
CI_IOS_OWNED are 1, the pool has two machines free, and the
glaeda-ios-sim entry of CI_OWNED_POOL_SLOTS leaves one simulator mini
free per device family. The janitor now counts jobs carrying that
capability label and a per-run capability marker. The build and
simulator jobs ask for [pool, glaeda-ios-sim]; mobile-core-package takes
the pool label alone. `runner: owned` forces the pool for a proof run.
ios_version runs, App Store uploads and release calls stay on Blacksmith.
The rescue watches test-ios and ios-screenshots dispatches like E2E and
re-runs stuck or refused jobs on retry_runs_on.

ios-streamed-validate and iroh-release-gate stay on Blacksmith but now
delete $HOME/.secrets/cmuxterm-dev.env in an always() step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 078f20a1-8b94-443d-9fb8-7f5dfd1595de

📥 Commits

Reviewing files that changed from the base of the PR and between 28147df and 45834e9.

📒 Files selected for processing (15)
  • .github/workflows/ci-owned-pool-rescue.yml
  • .github/workflows/ios-screenshots.yml
  • .github/workflows/ios-streamed-validate.yml
  • .github/workflows/iroh-release-gate.yml
  • .github/workflows/test-ios.yml
  • scripts/ci/e2e_runner_pool.py
  • scripts/ci/ios_runner_pool.py
  • scripts/ci/owned_pool_rescue.py
  • scripts/ci/pr_runner_pool.py
  • scripts/ci/queue_janitor.py
  • tests/test_ci_owned_pool_rescue.py
  • tests/test_ci_pr_runner_pool.py
  • tests/test_ci_self_hosted_guard.sh
  • tests/test_ci_workflow_run_sources.py
  • tests/test_ios_workflow_dispatch_ref.py
 ___________________________________________________
< Brace yourself. Winter is coming...for your bugs. >
 ---------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…code

Review fixes for the iOS owned-pool route:

- `runner: owned` now fails the runner job unless CI_PR_POOL_OWNED is 1
  (otherwise the rescue never watches the run and a queued job waits for
  good) and CI_OWNED_POOL_SLOTS gives glaeda-ios-sim at least one mini.
- seed_cache runs (ci-cache-writer environment, R2 write keys) never take
  an owned Mac, on auto or forced routes.
- A swift_package run holds one machine and no simulator, so it skips the
  glaeda-ios-sim capacity check and uploads no capacity marker.
- Jobs on a glaeda- label take CMUX_CI_XCODE_APP_PR, as test-e2e.yml does.
- fastlane snapshot builds into DerivedData under $RUNNER_TEMP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit bd018b1 into main Sep 25, 2026
58 of 59 checks passed
@teamleaderleo
teamleaderleo deleted the ci-ios-owned-pool branch September 25, 2026 05:33
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 25, 2026
5a81d71 Merge pull request manaflow-ai#14122 from manaflow-ai/issue-14037-window-display-hang
1a5be43 Merge pull request manaflow-ai#13020 from manaflow-ai/13016-sidebar-new-local-workspace
9e61fc2 ci: rebalance app-host shards from measured timings on all seven workers (manaflow-ai#14393)
8848a92 Merge pull request manaflow-ai#14044 from manaflow-ai/13648-ssh-switch-latency
caae250 Merge pull request manaflow-ai#13055 from manaflow-ai/13049-computer-use-onboarding
55dcb23 ci: let a warm owned Mac adopt a near seed instead of its kept build (manaflow-ai#14385)
e4e3d88 fix: harden warm reveal and CI array guards
ac5bdd9 Merge branch 'main' of https://github.com/manaflow-ai/cmux into 13049-computer-use-onboarding
d3865b3 Merge pull request manaflow-ai#14371 from manaflow-ai/14294-helper-staging-leak
bd018b1 ci: run unsigned iOS jobs on owned minis with counted simulator capacity (manaflow-ai#14389)
7ca7818 fix: avoid inheriting SSH cloud directories locally
f7c94b1 ci: run the dedicated step when a PR edits an env-gated test (manaflow-ai#14381)
566c83f ci: follow changed string literals in the reverse test impact report (manaflow-ai#14387)
87bf6ae fix(ci): skip installing the test module when emission is disabled
28147df Merge pull request manaflow-ai#14382 from manaflow-ai/14273-accessibility-children-cycle
4ff4cde Merge pull request manaflow-ai#14384 from manaflow-ai/12925-split-hint-stuck
bf65819 test: assert mounted sidebar and project AX reachability
55e4147 project: group drag tests beside their existing suite
2867b94 test: release MainActor while awaiting hint dismissal
5847394 fix(ci): handle empty app-host output batches
2c52835 Merge branch 'main' of https://github.com/manaflow-ai/cmux into 13648-ssh-switch-latency
e8dd4e0 test: update sidebar regression for scoped Cloud creation
63608eb Merge branch 'main' of https://github.com/manaflow-ai/cmux into 13016-sidebar-new-local-workspace
38ca93f Merge remote-tracking branch 'origin/main' into 14294-helper-staging-leak
209a484 Merge remote-tracking branch 'origin/main' into 14273-accessibility-children-cycle
9193381 fix: compare helper inventory independently of URL normalization
1c2fda2 fix: make sidebar AX queries preserve readable text without setters
373ed39 test: cover upgrades from legacy read-only helper generations
c7a8d40 fix: normalize managed helper directory modes before atomic publication
69827d4 Merge branch 'main' of https://github.com/manaflow-ai/cmux into 12925-split-hint-stuck
088d07e test: preserve sidebar text and forbid AX getter writes
245daa1 refactor: preserve helper installation errors for diagnostics
ce1d8bc test: isolate helper copy failure fixtures within tasks
b7cf47b Merge remote-tracking branch 'origin/main' into 12925-split-hint-stuck
53f6251 fix: scope split hints to the native drag lifetime
c2db719 fix: make helper replacement atomic and bound retries
2724342 Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
8280bd3 fix: handle optional restore bindings in workspace liveness
1f98d5e fix: prepare helper directory parent
fbad4c1 Merge remote-tracking branch 'origin/main' into 14273-accessibility-children-cycle
c59df55 fix: keep sidebar accessibility children acyclic
646d361 Merge remote-tracking branch 'origin/main' into 14294-helper-staging-leak
b9ab24f test: reproduce sidebar accessibility children cycle
7464b12 Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
b690bb1 test: keep canonical build guard stable across CI recipe changes
c58c708 test: cover file-drop hint lifecycle teardown
266fbc7 Merge remote-tracking branch 'origin/main' into 13016-sidebar-new-local-workspace
81f274f fix: make helper cleanup event driven
cd4a936 fix: reject malformed helper staging names
bdd08d0 Merge branch 'main' of https://github.com/manaflow-ai/cmux into 13648-ssh-switch-latency
0c23d10 Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
cfe4407 Merge remote-tracking branch 'origin/main' into 14294-helper-staging-leak
b1f4f89 fix: bound Computer Use helper staging
cdc90c7 test: reproduce helper staging leak
fdbcb3c Merge origin/main into 13049-computer-use-onboarding
c80b7be fix: avoid AppKit frame constrain reentry
21983c5 fix: guard display frame reconciliation against reentry
343b4fb chore: keep renderer changes within file budgets
d8e7469 fix: use warm reveal refresh policy in production path
a1baca1 chore: keep renderer extension within file budget
772d634 fix: retain warm frame state across terminal hides
b92dfdd fix: avoid redundant terminal refresh on warm workspace reveal
ff4795d Merge remote-tracking branch 'origin/main' into 13016-sidebar-new-local-workspace
f8c4493 fix: allow CUA from tagged dev Codex sessions
38e7acf fix: resolve post-merge restore build errors
5fd391f Merge origin/main into 13049-computer-use-onboarding
c9f363f fix: preserve nonblocking scoped feed telemetry
63378ac test: keep first-use Computer Use telemetry nonblocking and scoped
ed9c946 Merge branch 'main' of https://github.com/manaflow-ai/cmux into 13049-computer-use-onboarding
16bfc27 fix: consume relay origin before serializing the ordering environment
3321b39 test: validate relay barriers with the host admission parser
dcf085a fix: retain filtering for remote hook transports
3d60cd8 fix: keep relayed hook ordering out of local process routing
88cca73 test: retain relay origin in feed ordering barriers
126b2db Merge branch 'main' of https://github.com/manaflow-ai/cmux into 13049-computer-use-onboarding
75dde56 Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
cbf9edb fix: preserve relay origin through feed target resolution
24a7c8c test: cover relay-origin feed admission and first-use attachment
e167935 Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
23b1dfc docs: brand the provider as cmux Computer Use
12fd7b0 Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
c4f611a fix: restore Swift parameter separators
203bec4 fix: validate both helper profiles before readiness
a31f9a6 fix: refresh revocation before first-use admission
9e9df10 fix: keep first-use credentials and revocation state current
d5e9bf4 fix: preserve readiness and relay feed admission
d3e906b fix: restore scoped completion before daemon readiness
e17514c test: use the direct capture outcome API
ec9b6e8 fix: report stale capture verification as unavailable
1260836 fix: keep relay routing and Swift 6 compatibility fail closed
267168e Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
edf1834 fix: expose shared feed target resolver to CLI extensions
b4f816d fix: bind onboarding work to view task lifecycle
55ffd3e fix: close Computer Use onboarding admission races
03772a9 test: expose Computer Use onboarding review regressions
5ddf044 Merge remote-tracking branch 'origin/main' into 13049-computer-use-onboarding
a61e770 fix: restore Computer Use test API visibility
5b8d894 fix: expose setup status for Settings snapshot
811990b fix: restore onboarding completion key compatibility
797bbac fix: wire Computer Use Settings host actions
78128dd fix: expose onboarding completion status to UI
b40ea20 fix: expose package transport to capture verification
b1d2670 fix: expose helper startup to capture admission
f8ea3c8 fix: expose runtime seams to package onboarding adapters
138d703 fix: import package transport types for capture verification
72966a2 fix: resolve feed targets through live delivery
e7231ca test: restore Computer Use onboarding target wiring
f0c6c1e fix: adapt Computer Use onboarding to current main architecture
66de110 Merge origin/main into 13049-computer-use-onboarding
bb1d403 fix: close remaining onboarding review findings
d8cff69 docs: document Computer Use core contracts
f9d1a3c docs: describe automatic Computer Use setup
a12ef64 fix: close Computer Use onboarding review gaps
6cb9cf2 Merge origin/main into 13049-computer-use-onboarding
1021c83 fix: notify Computer Use directly at feed ingress
ba61825 fix: present onboarding before helper provisioning
7c0443f fix: accept live owned surface for first-use onboarding
7db2f4b fix: recognize all owned terminal surfaces for first-use setup
73e3144 fix: opt into Computer Use setup from first explicit request
dce767a test: reproduce lost Computer Use hook surface in built CLI
4b40d82 fix: present setup before live session indexing
a8d61c5 fix: make cmux-cua the only Codex computer provider
c0773d9 fix: await live session indexing before first-use setup
82efcbf fix: open Computer Use setup on the first functional tool request
53256c9 test: require setup presentation on the first Computer Use tool
b6625cd fix: recheck grants through the shared daemon control protocol
ac17c49 fix: invalidate stale capture proof and roll back partial admission
1c21a39 fix: keep Computer Use setup status live through completion
1146f41 fix: make Computer Use setup completion runtime-owned and recoverable
3c44d5c test: reject stale Computer Use onboarding completion after disable
b144586 fix: make sidebar New Workspace explicitly local
3e77548 test: cover local workspace creation from sidebar plus menu

# Conflicts:
#	.github/workflows/ci-macos.yml
#	.github/workflows/ci-owned-pool-rescue.yml
#	.github/workflows/ci.yml
#	.github/workflows/ios-screenshots.yml
#	.github/workflows/ios-streamed-validate.yml
#	.github/workflows/iroh-release-gate.yml
#	.github/workflows/test-ios.yml
teamleaderleo added a commit that referenced this pull request Sep 25, 2026
…run for ios-screenshots.yml

#14389 made test-ios.yml and ios-screenshots.yml rescue sources.
test-ios.yml gets an owned-pool-watch job. ios-screenshots.yml cannot hold
one (release.yml calls it with contents: read, #12149), and it had no run
from 09-23 to 09-25, so the rescue keeps a workflow_run trigger for it alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant