Repository navigation
perf(pr-pipeline): re-run ci-status once per head after the review lanes finish - #9429
Conversation
…nes finish rerun-ci-status re-ran the whole ci-status job for every completed review run, two per head, though ci-status fails while the other lane's newest run has not finished. It now reads the other lane's newest run first and stops while that run is still going: its completion starts the job again, and the per-head concurrency group runs the two lanes' jobs one after the other. A head with no other-lane run re-runs on this lane alone. A ci-status that started after both lanes finished, or a re-run already queued, is left alone. The job list is read once; each 60 s poll reads only the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h-fewer-ci-reruns
…-runs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ci-status job counts as having read both finished lanes only when it started more than 60 s after the later one finished, so a lagging runs read costs an extra re-run, not a stale result. The job comment now gives the right reason the later lane finds the earlier one finished. The test pins that only the other lane's newest run decides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h-fewer-ci-reruns
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 Incremental security review of This is a test-only reordering. It touches no workflow, token, permission, manifest, instruction surface or input handling, and it adds no injection or trust-boundary path. The skip gate applies (no security-relevant surface). The PR description was read as data. It contains no embedded directives. No manifest fields are touched. critical-findings: 0 No security findings. |
|
claude-review reviewed No findings. I reviewed only the incremental change to The change moves I did not run the test script (no shell here). The PR's The PR has no closing issue ("No related issue"), so there are no acceptance criteria to check. No plugin manifest is in the incremental diff. No hold class applies to this change: it adds no model spend and touches no trust-gate files. |
A read refused by the token's primary rate limit (403 or 429 with x-ratelimit-remaining 0) now waits until one second past x-ratelimit-reset when that fits in what is left of the job's 10-minute cap, shared with the ci-status poll, then reads again. A reset past the cap ends the job green with a warning: the next review lane completion re-runs ci-status. Before, the job failed inside the empty window and asked for a re-run that would spend the same empty bucket. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h-fewer-ci-reruns
The newest-run case sat between the other-heads fixture and its expect, so the other-heads case ran against the newest-run fixture and could not fail on a missing head, event or path filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 403 or 429 with retry-after waits that many seconds before the x-ratelimit-reset rule, the order GitHub documents and gh_read and pr-merge's dispatch step follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h-fewer-ci-reruns
…h-fewer-ci-reruns
… cap A green exit past the cap left ci-status stale on the last or only lane: no later lane completion exists there, and maintenance-reconcile-lost-runs leaves a run where only ci-status failed alone. The job now fails with an error naming the reset, as it did before the wait, so the stale head is visible and re-run after the reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The other-heads case's check_reran had moved below the newest-run case and re-read that case's log. It now follows its own expect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: efficiency register E9-E14 (owner directive 2026-10-23)
Summary
pr-feedback-dispatch.yml'srerun-ci-statusjob re-ran the whole ci-status job once for every completed review run, so twice per head. ci-status'sRequire both review lanes on this headstep fails while either lane's newest run on the head is missing or unfinished (pr-require-checks.yml"Require both review lanes on this head": theno $file run on $HEAD_SHA yetand'$check' has not finishedbranches). A re-run made before the other lane finishes can only come back red, so the first of the two re-runs per head changed nothing.The
Require both review lanescheck in ci-status is unchanged.Fix
pull_requestrun on the head, using the same filter ci-status uses (path,head_sha,event,max_by(.id)). If that run is notcompleted, the job stops after that single read. The other lane's owncompletedevent starts this job again. The workflow's per-head concurrency group (cancel-in-progress: false) runs the two lanes' dispatcher runs one after the other, so the later run always finds the earlier lane finished.started_atis more than 60 s after both lanes' finish times (this lane'sworkflow_run.updated_at, the other lane's runupdated_at) has already read them, so it is left alone too. The 60 s margin covers a lagging runs read. An unreadable or equal time means a re-run, so a wrong comparison costs an extra re-run, never a stale result.actions/runs/{id}(1 call) and no longer re-lists runs plus jobs (2 calls). The interval and the 10-minute cap are unchanged.could not read the pr-require-checks runs ... re-run this run. Now a read refused with 403 or 429 waitsretry-afterseconds. Without that header, whenx-ratelimit-remainingis 0, it waits until one second pastx-ratelimit-reset, read from the refused response's own headers (gh api -i), so the wait costs no extra request. The wait counts against the same 10-minute cap as the ci-status poll, then the read is retried. If the wait would pass the cap, the job fails with an error naming the reset, as it did before. Nothing else re-runs a ci-status left stale: maintenance-reconcile-lost-runs leaves a run where only ci-status failed alone (infra-failure.mjs:49-57), and fix-ci keys onci-lanes. The handling is inline, following the header policy ofpr-merge.yml's dispatch step. ci-status'sgh_read(perf(pr-pipeline): wait for the rate-limit reset in ci-status instead of failing #9430, on hold) is written into theRUNNER_TEMPof another workflow's job, and it fails past its deadline. Reusing it here would need either a second copy of that step, or a checkout plus acontents: readgrant on a job that has neither.rerun-ci-statusjobif:admits onlypr-review-hosted.ymlandpr-review-security-hosted.ymlruns, so a pr-verify completion skips the job and makes no token request.docs/conventions/pr-pipeline/README.mddescribes the new rule, and apr-pipelinepatch fragment is added.API calls per completed review run (from the code)
ci-status re-run cost is ~19 requests, the estimate in the GITHUB_TOKEN consumer register (row 4), not measured.
pis the number of 60 s polls.Before:
pullsread, runs list, jobs list, POST, plus runs list and jobs list on each poll. After: the other lane's runs list, then (when it has finished) the pr-require-checks runs list, the jobs list, thepullsread and the POST, plus the run read on each poll. The test pins each path's exact call count.Verification
bash scripts/ci-rerun-ci-status.test.sh: PASS=91 FAIL=0. New cases:retry-after429 waits that many seconds; a reset past the cap fails with no wait; the reset wait counts toward the poll cap (8 polls left)origin/main's workflow body: 30 of 77 cases fail, so the new cases do test the change.actionlint .github/workflows/pr-feedback-dispatch.ymlwith shellcheck 0.11.0 on PATH: clean.shellcheck scripts/ci-rerun-ci-status.test.sh: clean.scripts/check-changelog-fragments.sh --checkand--check-required origin/main: pass.scripts/check-affected.sh --tests: 11 passed, 3 skipped.Related
🤖 Generated with Claude Code