ci(pickers): count each picker's GitHub API requests per token and print the rate-limit headers
Measures each CI picker's GitHub API spend, per token, in every run. Nothing about routing changes.
Why. The routing App's installation ran out of REST quota on 2026-11-01. In run 41344566214 every read in cmux-next macOS placement returned 403 "API rate limit exceeded for installation ID 164690637", so the placement picked blind and sent the feat-cmux-next Release compile to Blacksmith macOS. The same exhaustion blinds the nightly build, sign and verify pickers. Nothing measured which picker spends what, or what the installation's real budget is. A static read of the code puts macos-placement at 10-13 App requests per invocation (about 170 cmux-next runs an hour), and the nightly pickers at about 40-90 per nightly run, with sign-owned's run and job listings the largest. Those are estimates. Cutting the spend (one runner read per run, and fewer listings in sign-owned) needs measured numbers first.
What. scripts/ci/github_spend.py records each request made through pr_runner_pool.GitHub (every picker built on it: side-lane placement, the nightly build, sign and verify pickers, signing_runner) and next_macos_cascade.Api. At exit it prints one stderr line per token, for example:
GitHub API spend: token 1a2b3c4d: 11 requests (0 refused); core limit 5000, remaining 812
- Each token is named by a short hash, never printed.
- The line goes to stderr, because some pickers' stdout is appended to
$GITHUB_OUTPUT. recordnever raises.- A step that made no request prints nothing.
- The picker jobs' sparse checkouts list the new module, without which
test_ci_nightly_push_throttlefailed.
Next. From one nightly run and one cmux-next push run at this head, I'll take the per-job counts and the installation limit, then cut the largest spenders in a follow-up PR with a before/after Performance table.
Performance
| metric | before | after | delta | harness or run link |
|---|---|---|---|---|
| App REST requests per picker step | not measured | printed per token in each step's log | - | measurement only; the cuts follow in a later PR |
Tests: new tests/test_github_spend.py (wired in ci-guards and test-execution.toml). These pass: test_ci_pr_runner_pool, test_ci_next_macos_cascade, test_ci_side_lane_placement, test_ci_portable_job_placement, test_ci_signing_owned_mac, test_ci_queue_janitor, test_ci_route_refusal_detail, test_ci_simple_pool_picker, test_ci_owned_warm_state, test_ci_rescue_actors, test_ci_runs_on_fallback, test_ci_fork_runner_routing, test_nightly_trusted_routing, test_ci_nightly_push_throttle, test_nightly_build_runner, test_nightly_sign_runner and test_workflow_file_size. test_ci_test_execution_registry fails the same way on base in this environment (a git push the sandbox refuses).
Agent session: agent session
š¤ Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Measures each CI picker's GitHub API spend per token and prints the rate-limit headers at exit, so spend can be cut based on measured numbers instead of estimates.
The routing App's installation ran out of REST quota on 2026-11-01 (403s in run 41344566214), and nothing measured which picker spends what. scripts/ci/github_spend.py records each request made through pr_runner_pool.GitHub and next_macos_cascade.Api; when the step exits, the log gets one line per token with request count, refused count, and the last limit/remaining seen.
- Tokens are never printed, only a short hash.
- The report goes to stderr and never raises; a step with no requests prints nothing.
- Adds
tests/test_github_spend.pyand lists the new module in picker jobs' sparse checkouts.