ci: count the org glaeda-minis runners in the live pool picker
glaeda#1222 moved the Mac minis to org runners (org manaflow-ai, runner group glaeda-minis). The live pool picker (#14350) lists runners through GET /repos/manaflow-ai/cmux/actions/runners, which returns only repository runners: today that is the two trusted seed minis and eight offline tart VMs. So the live path counted every PR-pool mini as busy.
Evidence: run 36134461049 (#14584) logged 0 of 32 owned machines free ... 0 of 15 root runners free with no "could not list runners" warning, while GET /orgs/manaflow-ai/actions/runner-groups/23/runners showed 27 of 44 group runners online and idle at the same time.
Change
pr_runner_pool.GitHub.runners()now returns the repository's runners plus the runners of the org groupglaeda-minis(found throughGET /orgs/{org}/actions/runner-groups?visible_to_repository=cmux, so only a group this repository can use), deduplicated by runner id. That is 3 requests. Listing all org runners instead would be 28 pages: the org has about 2,750, mostly stale Blacksmith entries.- Every caller shares that function: the PR picker, late placement (
late_placement.py), and the iOS and E2E pickers in #14542 and #14584. None of those files change here. - If the group can't be read (HTTP 403, or no
glaeda-minisgroup visible),runners()raisesorg runner group glaeda-minis unreadable (HTTP 403); the routing App needs the organization permission Self-hosted runners: read. Each caller already catches that and falls back to the snapshot, so a missing permission no longer shows up as "every mini busy". ci.ymlandci-macos.yml: the route token also requestspermission-organization-self-hosted-runners: read.
App permission (Leo)
Grant App 5067688 (manaflow-glaeda-route) "Organization permissions -> Self-hosted runners: Read and write" (write for the warm labels in the follow-up PR), then approve it on the manaflow-ai org install.
Until then the mint is all or nothing: this PR's CI (run 36174483281) shows The permissions requested are not granted to this installation (422) on the first mint. So a second step, route-token-repo, runs only when the first failed and asks for permission-administration: read alone, as before. The pickers use whichever token came out. With the repository-only token, runners() gets a 403 on the group and every caller falls back to the snapshot, which is better than today's false zero. Once the permission is granted, the first mint succeeds and the picker logs live idle counts.
A runner group entry without an id is skipped rather than requested as runner-groups/None.
Tests
tests/test_ci_pr_runner_pool.py adds these cases, using fake org API responses:
- repository runners and group runners merged, deduplicated, and counted idle
- paging through a full group
- a 403 and a missing group each raising with a clear message
- both mint steps requesting the org permission
The late placement, warm labels, E2E and janitor suites pass unchanged.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
Summary by cubic
Fixes the live pool picker counting every glaeda mini as busy by also listing the org glaeda-minis runner group.
- The minis moved to org runners (glaeda#1222), so the repository runners endpoint no longer shows them;
GitHub.runners()now merges the group's runners visible to this repository, deduplicated by id. - When the group can't be read (HTTP 403 or missing),
runners()raises and each caller falls back to the snapshot, so a missing permission no longer shows up as zero free machines. - The route-token mints in
ci.ymlandci-macos.ymlrequest the organizationSelf-hosted runners: readpermission; grant it to the routing App and approve the installation update for live counting to turn on, otherwise the picker keeps using the snapshot.