Repository navigation
perf(ci): measure unattributed GITHUB_TOKEN calls and cut three without changing behavior - #10141
Conversation
Measurement first (report row 8 and the 41% unattributed, about 1,850 core/h): the rate guard every Node GitHub call goes through now keeps a reading of each answer under API_BUDGET_TOKEN, and the log-api-budget action adds an exclusive `api-calls <token>=N not-modified=M` line next to the shared-counter cost. Wired into intake-triage born-triaged and refine-select, pr-verify check-intent and check-intent-subscription (step-level env; the log step sits before "Fail without a verdict"), pr-unblock's four select/context jobs, post-merge-demotion-alert report-red-main, and the audit-lane-runs, delete-stale-branches, monitor-signals and reconcile-lost-runs jobs. Estimated saving: 0 calls/h; it turns the estimates into measurements. Behavior unchanged: recording writes a file under RUNNER_TEMP only when the step names API_BUDGET_TOKEN, and adds no GitHub call. budgetedFetch drops its own recordReading so the guard is the one recorder (its existing test pins one line per answer). recordReading moves into rate-guard.mjs (api-budget.mjs re-exports it) so the HIGH-floor import closure does not grow. Not covered: intake-triage's main job (a ci-workflows reusable) and bash `gh` calls left unwrapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report cut 7 (estimate 20-40 core/h). The poll of the pr-require-checks run (pr-feedback-dispatch.yml rerun-ci-status, every 60 s up to 600 s) now sends each repeat read with If-None-Match on the last 200's ETag; a 304 (gh exits 1 on it) returns that 200's body. The brief's "one wait" or event-driven path would change when ci-status is re-run, so the poll keeps its pace, cap and decisions; only the answer form changes. Saving: up to (polls - 1) calls per waiting run, but NOT claimed: GitHub documents that an authorized 304 does not count against the primary limit, which is unverified for GITHUB_TOKEN. The 304 readings land in this job's api-budget notice (api-calls ... not-modified=M with x-ratelimit-used), the measurement path that settles it on the first live run. Behavior unchanged: same reads, sleeps and outcome; a 304 stands for an unchanged run. Pinned by scripts/ci-rerun-ci-status.test.sh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report cut 10 (estimate 15-40 core/h). The wait step reads the dispatched full run every 180 s, 13 times at most. Each read after the first now carries If-None-Match on the last ETag; a 304 makes gh exit 1, which the loop already treats as "read again next time", and a 304 can only mean the run is unchanged, so still not completed. The brief's "wait once" would hold the push group past the run's end and delay the red-run dispatch, a behavior change, so the pace and ceiling stay. Saving: up to 12 calls per merge burst, NOT claimed: an authorized 304 not counting is documented but unverified for GITHUB_TOKEN; the rerun-ci-status readings settle it. Behavior unchanged: same reads, sleeps, group hold and dispatch. A small hunk in the wait step only, to ease the concurrent pr-merge.yml change. Pinned by pr-merge workflow.test.mjs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Coordinator cut (b), per_page=100 on paginated reads that use the default 30. A sweep of .github found one: report-escalation.sh's `gh api --paginate "$issue/comments"`; the shared Node paginate() and the other bash --paginate reads already ask for 100. Saving: ceil(n/30) - ceil(n/100) calls per escalation on a pull request with n comments (0 under 31 comments, 1 at 31-60). That read runs only outside status mode, which the lanes App token drives, so it saves App calls, not GITHUB_TOKEN ones; estimate under 1 call/h. Behavior unchanged: --paginate still reads every page and jq -s merges them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis 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. |
|
claude-security-review reviewed No security findings in this pull request. critical-findings: 0 Scope read: all 22 files of the diff and the PR context. No instruction-surface deletions and no plugin manifests ( Trusted paths. No file under
Shell changes.
Workflow changes.
Cost. None. The new steps spend a few seconds of existing Actions minutes. The change removes no calls and adds none. |
|
claude-review reviewed No findings on What I checked by reading:
Spend: the added |
Conflicts composed: - .github/workflows/pr-merge.yml (wait for the full run): keep main's ETag-conditional `gh api -i` poll (#10141) and wrap it in this branch's stderr capture, so a rate-limited read still stops the wait red while a 304 (gh exit 1, no rate-limit text) keeps polling. - .github/scripts/pr-merge/workflow.test.mjs: runWait takes both notModified and limited/limitedMessage; reads is main's array of read lines, so the rate-limit tests assert reads.length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: GITHUB_TOKEN budget work against the owner's 1,000/h plan (R26). The study ran in the session and was not filed.
Summary
GitHub documents
GITHUB_TOKENat 1,000 REST requests per hour per repository (REST rate limits). The lanes plan to that number, and the observed 5,000 header limit is headroom, never a target.The 2026-10-25 budget study measured core on two hourly counters with different reset minutes. Over 07:00 to 15:00Z their mean window peaks were 2,674 and 1,792, about 4,470/h combined. That is roughly 4.5 times the plan. About 1,850 core/h (41%) came from jobs that post no api-budget notice, so it could not be attributed.
This PR has two parts. First, it measures what was unattributed. Second, it ships the three cuts that leave behavior unchanged. Cron cadence is not changed here. The pr-merge.yml edit is one wait-step hunk, and the rest of that file belongs to the concurrent pr-merge work.
Fix
API_BUDGET_TOKEN. The log-api-budget action adds an exclusiveapi-calls <token>=N not-modified=Mline next to the shared-countercost. The notice is wired into intake-triage born-triaged and refine-select, pr-verify's two judge jobs, pr-unblock's four select/context jobs, post-merge-demotion-alert report-red-main, and the audit-lane-runs, delete-stale-branches, monitor-signals and reconcile-lost-runs jobs.If-None-Matchon the last ETag. A 304 stands for the last 200's body. Pace (60 s), cap (600 s) and decisions are unchanged.GITHUB_TOKENis unverified. The newnot-modifiedcount settles it on the first live run.--paginatecomments read asks for 100 per page, not the default 30.Changelog fragment:
.changes/pr-pipeline/perf-cut-github-token-calls-3f9a1c62.md(patch).Verification
rate-guard.test.mjs: main's stop-file tests and this branch's reading test were both added. The resolution keeps both, with the union of theirnode:fsimports.node --testover every*.test.mjsthe branch touches or that names a touched workflow or script: 2256 tests, 2256 pass.scripts/ci-rerun-ci-status.test.sh: 157 pass, 0 fail..github/scripts/pr-unblock/report-escalation.test.sh: 571 ok, 0 failed.scripts/check-changelog-fragments.sh --checkpasses.Related
feat/api-pool-limit-capaddslimits.api.github-token.limit-cap(default 1000), which the rate guard plans to.🤖 Generated with Claude Code