Product

Your own runners

Jobs are matched to machines by capability, not by name. An Intel Mac and an Apple-silicon one are not the same machine with a different sticker, and scheduling that pretends otherwise produces a queue that mysteriously stops moving.

Xcode is a capability, not an install step

Every other toolchain a build needs — Node, Flutter, Ruby, the JDK — is installed and cached on demand from your own pins: .nvmrc, .fvmrc, .ruby-version, the Gradle wrapper, packageManager.

Xcode is the exception and it is not a limitation we intend to remove. It is far too large to install on demand, so a job that needs a version is matched to a machine that already has it — and a job that needs a version no machine carries waits, visibly, rather than failing halfway through a download.

Speed is a scheduling input

Each runner carries a concurrency and a measured speed weight, and the product of the two is what it actually contributes. Three slots at full speed and two slots at a third are not “three and two” — they are 3.0 and 0.7, which is why one of those machines going offline is a page and the other is a warning. The console says which is which.

Fork pull requests, on a pool of their own

A pull request from a fork runs on the untrusted pool: zero credentials, no distribution, an unsigned build. That is not a setting — it follows from what the steps in the job resolve, and is computed rather than declared. The step library shows the derivation for every step.

What this part refuses to do

Each of these could be built, and each would make the product worse.

Supplying the Macs
You bring the machines, on Cloud as well as self-hosted. It is a constraint, and it is also why nothing here gets more profitable when your builds get slower.
Assuming arm64 for Android emulators
An emulator system image has to match its host’s architecture. The rule is not “always arm64”, and a scheduler that hardcodes one boots nothing on half the fleet.