Repository navigation
fix(pr-pipeline): stop a GitHub API pool at its stop floor, not only at zero - #15305
Conversation
…at zero The Node rate guard warned under the read floor and kept sending until GitHub refused a call. On 2026-11-11 the GITHUB_TOKEN window went from 475 remaining to 0 in eight minutes and GitHub answered 403 "API rate limit exceeded for installation". A response whose x-ratelimit-remaining is at or under the pool's limits.api stop-floor share of x-ratelimit-limit (default 0.02 in rate-limits.json) now latches like a remaining of 0: the stop goes in the job's stop file, later calls to the pool are refused until the header's reset, and the step holds under API_BUDGET_HOLD, else fails. A response with no readable limit, remaining or future reset never stops on it. The check reads headers already returned, so it adds no API call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eff99eb4a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
claude-security-review reviewed Security review: PR #15305, head
|
|
claude-review reviewed Code review: stop a GitHub API pool at its stop floorNo blocking or advisory findings. I read every hunk of all 7 changed files. I also read the unchanged code the changed lines call in What I checked
Closing issueNo closing issue: the PR cites hub lessons-ledger finding L532, so there are no issue acceptance criteria to check. The changelog fragment's Notes
|
|
pr-unblock / address-feedback finished round 1 of max 4 on eff99eb. |
|
The pr-unblock lane stopped on this pull request and needs a person: an adjudicator found that a disputed review finding turns on a design choice, which a person decides. Reason:
To resume, deal with the reason above, remove the gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=15305 -f activity=address-feedback |
No related issue: hub lessons-ledger finding L532 (breaker trip leak, GitHub 403 on 2026-11-11 07:26:48Z)
Summary
On 2026-11-11 the GITHUB_TOKEN window went from 475 remaining at 07:17Z to 0 at 07:25Z, and GitHub then answered 403 "API rate limit exceeded for installation". The Node rate guard only warned under its read floor ("reads continue") and stopped a pool only after GitHub refused a call. GitHub's docs say "Continuing to make requests while you are rate limited may result in the banning of your integration" (rate limits for the REST API).
Fix
.github/actions/log-api-budget/rate-guard.mjs:classifytakes astopFlooroption (off by default, so the shared vectors and the shell and Python ports are unchanged). The latch passeslimits.api.<pool>.stop-floor, read through the samemargin()path asread-floor. A response whosex-ratelimit-remainingis at or under that share ofx-ratelimit-limitnow latches like a remaining of 0: the stop is written to the job's stop file, later calls to that pool are refused untilx-ratelimit-reset, and the step holds underAPI_BUDGET_HOLD, else fails. The log line says it stopped at the floor ("left GitHub's core API 100 of 5000 requests, at or under its stop-floor share 0.02"), so nobody reads it as a refusal. A response with no readable limit, remaining or future reset never stops on the floor (fail open). The refusal path is unchanged..github/actions/log-api-budget/rate-limits.json:stop-floor: 0.02for every pool, mirrored in the built-inSHARES. No existing share,TRIP_PCTorCLEAR_PCTchanged.docs/conventions/pr-pipeline/pr-pipeline.schema.json:stop-flooradded toapi-poolandapi-content-pool(bothadditionalProperties: false), so alimits.apioverride inpr-pipeline.yamlvalidates. Descriptions in the schema, the READMEapirow and thepr-pipeline.yamlcomment are updated where they said the guard stops only at zero..changes/pr-pipeline/fix-rate-guard-stop-near-zero-d2c8e526.md(minor: a new config field).Cost per run: no added API calls. The check reads rate-limit headers on responses the guard already receives.
Verification
node --test --test-concurrency=4 ".github/actions/log-api-budget/*.test.mjs" ".github/actions/resolve-config/*.test.mjs": 1248 pass, 0 fail.rate-guard.test.mjs(threshold 100 = 0.02 x 5000, by hand): remaining 100, 99 and 1 stop, recordcore 1792751600in the stop file, refuse the next call and fail the step; 101 sends on with no error; a holding latch logs a notice and fails nothing; missing limit, unparseable remaining or limit, missing reset, or a reset already past never stop; alimits.apioverride of 0 stops nothing at 1 left; a 403 refusal still logs "hit GitHub's core API rate limit".python3 -m unittestontest_babysit_gh_rate_guard.py: 46 pass.Related
work-resolve-issue.yml'sadmitandpick-slotjobs. A run stopped there would end withadmitgreen and its blocker step skipped, whichdidNoWorkin.github/scripts/work-unblock/label-unblocked.mjsclassifies as no stall, so work-unblock's sweep would never re-fire the issue once the switch clears. Today such a run reaches broker-check's kill-switch step and is re-fired as a breaker trip. That change needs the sweep to learn the new stop first; it is left to the hub.api-budget.bash(the shell guard) andbabysit_gh.pystill stop only at zero; both read the shared vectors, which this change leaves alone.session: GitHub Support
🤖 Generated with Claude Code