Why macOS jobs queue on GitHub Actions, and how to cut the wait
"Queued" on a macOS job hides three different waits. Fixing the wrong one is the usual reason a team spends money and sees no change.
1. Waiting behind your own concurrency
GitHub caps concurrent macOS jobs per plan. Above the cap, jobs wait for your own jobs to finish. A matrix of eight simulator lanes on a cap of five will always have three lanes waiting, no matter how fast the machines are. Look at the run's timeline: if jobs start in batches, this is it. Fixes: fewer lanes, concurrency: groups that cancel superseded runs, or more purchased concurrency.
2. Waiting for the shared pool
Hosted macOS capacity is a shared pool. At busy hours the gap between "queued" and "runner assigned" grows even when you are under your cap. You cannot fix this from the workflow; you can only move to capacity that is reserved for you.
3. Waiting for the VM
A fresh macOS VM has to boot and register with GitHub before the first step runs. This is visible as the gap between "runner assigned" and "Set up job". On Manzanita this is the number the dashboard calls time to runner: 5 s median and 9 s p95 across the fleet for the 30 days ending September 5, 2026, measured from the moment a dispatcher starts provisioning to the VM being ready (definition). VMs are cloned from a prewarmed image on the host that will run them.
Measure before you change anything
For any job, GitHub exposes created_at, started_at and completed_at:
gh api repos/OWNER/REPO/actions/runs/RUN_ID/jobs \
--jq '.jobs[] | [.name, .created_at, .started_at, .completed_at] | @tsv'
started_at − created_at is the total wait. If it is large and your run had fewer jobs than your cap, it is the pool. If it correlates with how many jobs you launched, it is your cap.
What Manzanita changes
- Wait #2 disappears: each purchased runner is capacity reserved for your organization.
- Wait #3 is about five seconds median once a runner is assigned.
- Wait #1 becomes explicit. The Jobs page shows capacity wait (behind your own runners) separately from VM start, so you know whether to buy a second runner or trim the matrix.
Jobs above your concurrency still queue in GitHub with normal logs; nothing is dropped.
runs-on to manzanita-standard and keep everything else. Start free · Read the docs