Skip to content

perf(pr-pipeline): re-run ci-status once per head after the review lanes finish - #9429

Merged
kyle-sexton merged 13 commits into
mainfrom
perf/feedback-dispatch-fewer-ci-reruns
Oct 23, 2026
Merged

kyle-sexton merged 13 commits into
mainfrom
perf/feedback-dispatch-fewer-ci-reruns

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

No related issue: efficiency register E9-E14 (owner directive 2026-10-23)

Summary

pr-feedback-dispatch.yml's rerun-ci-status job re-ran the whole ci-status job once for every completed review run, so twice per head. ci-status's Require both review lanes on this head step 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": the no $file run on $HEAD_SHA yet and '$check' has not finished branches). 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 lanes check in ci-status is unchanged.

Fix

  • One re-run per head, after the last lane. The job first reads the other lane's newest pull_request run on the head, using the same filter ci-status uses (path, head_sha, event, max_by(.id)). If that run is not completed, the job stops after that single read. The other lane's own completed event 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.
  • Single-lane heads still re-run. When the other lane has no run on the head (an exempt author, or a lane that never started), or its run completed with any conclusion (skipped, cancelled or failure), this lane counts as the last one and the re-run happens.
  • No duplicate re-run. A ci-status that has not started yet is left alone, as before. This includes a re-run another dispatcher run already queued. A ci-status whose started_at is more than 60 s after both lanes' finish times (this lane's workflow_run.updated_at, the other lane's run updated_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.
  • Job list read once. The job list is still needed for the ci-status job id and its not-started state, but it is now read once. Each 60 s poll reads only actions/runs/{id} (1 call) and no longer re-lists runs plus jobs (2 calls). The interval and the 10-minute cap are unchanged.
  • A rate limit no longer fails the job. At the 2026-10-23 15:28Z exhaustion, run 39978571225 failed in this job with could not read the pr-require-checks runs ... re-run this run. Now a read refused with 403 or 429 waits retry-after seconds. Without that header, when x-ratelimit-remaining is 0, it waits until one second past x-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 on ci-lanes. The handling is inline, following the header policy of pr-merge.yml's dispatch step. ci-status's gh_read (perf(pr-pipeline): wait for the rate-limit reset in ci-status instead of failing #9430, on hold) is written into the RUNNER_TEMP of 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 a contents: read grant on a job that has neither.
  • pr-verify completions start no re-run. This was already true: the rerun-ci-status job if: admits only pr-review-hosted.yml and pr-review-security-hosted.yml runs, so a pr-verify completion skips the job and makes no token request.
  • PR state check moved last. The PR open/head check now runs just before the POST, so the "other lane pending" path costs one call.
  • Docs and changelog. docs/conventions/pr-pipeline/README.md describes the new rule, and a pr-pipeline patch 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. p is the number of 60 s polls.

Path Before After
Other lane still running (first lane of a head) 4 + 2p + one ci-status re-run (~19) = ~23 + 2p 1, no re-run
Last lane, or single-lane head 4 + 2p + ~19 = ~23 + 2p 5 + p + ~19 = ~24 + p
ci-status already fresh, or re-run already queued 4 + 2p + ~19 (it re-ran anyway) 3, no re-run
Per head (two lanes) ~46 + 4p, two ci-status re-runs ~25 + p, one ci-status re-run

Before: pulls read, 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, the pulls read 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:
    • other lane in_progress, queued or waiting: no re-run, 1 call
    • both lanes done: 1 re-run, 5 calls
    • no other-lane run (single-lane head): 1 re-run, 5 calls
    • other lane completed as skipped, cancelled or failure: re-run
    • triggered from either lane path
    • ci-status started after both lanes, or a re-run already queued: no re-run, 3 calls
    • stale ci-status in progress: polled reading only the run (7 calls with 2 polls, 13 at the 10-minute cap), job list read once
    • a bad lane path or finish time fails before any read
    • ci-status started within 60 s of the last lane: re-run
    • an older unfinished other-lane run does not hold the newest finished one
    • primary rate limit: waits until the reset (120 s here), then re-runs; a retry-after 429 waits that many seconds; a reset past the cap fails with no wait; the reset wait counts toward the poll cap (8 polls left)
  • The first version of this test, run against origin/main's workflow body: 30 of 77 cases fail, so the new cases do test the change.
  • actionlint .github/workflows/pr-feedback-dispatch.yml with shellcheck 0.11.0 on PATH: clean.
  • shellcheck scripts/ci-rerun-ci-status.test.sh: clean.
  • scripts/check-changelog-fragments.sh --check and --check-required origin/main: pass.
  • scripts/check-affected.sh --tests: 11 passed, 3 skipped.
  • Local code review and security review (fresh-context subagents): no correctness or security findings. Three suggestions were applied: the corrected concurrency comment, the 60 s margin, and the newest-run test. The CI code-review lane's thread about a test fixture being overwritten was valid; it is fixed in 7e622de and resolved. Its second thread pointed out that a green exit past the cap stranded the last lane; it is fixed in 540b163 and resolved.

Related

  • Efficiency register items 1 and 8, table rows 4 and 7 (GITHUB_TOKEN consumers, 2026-10-23).
  • Variant (b) in that register (each lane writes its own commit status, so there is no whole-job re-run) is not attempted here.

🤖 Generated with Claude Code

kyle-sexton and others added 5 commits October 23, 2026 11:05
…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>
…-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>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 23, 2026 15:21
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T15:23:08.740935Z 643931f Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 41be7ff9ae561836c768cef78b2c49d9ad3d8b4c: 0 finding(s), 0 as inline comments.

Incremental security review of scripts/ci-rerun-ci-status.test.sh (since 540b163). The change moves one check_reran 1 "a head with no other-lane run of its own is re-run" assertion so it sits directly after the "other heads, events or files" case it describes. The old position, after the newest-run case, left it checking the wrong stub state.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 643931f77443f536682eb09b24bc0e5d71086875; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread scripts/ci-rerun-ci-status.test.sh
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed 41be7ff9ae561836c768cef78b2c49d9ad3d8b4c: 0 finding(s), 0 as inline comments.

No findings. I reviewed only the incremental change to scripts/ci-rerun-ci-status.test.sh, and read the surrounding test helpers (expect, check_reran) for context.

The change moves check_reran 1 "a head with no other-lane run of its own is re-run" up to directly after the "other heads, events or files are not the other lane's newest run" expect (now line 189). Before, it sat after the "older other-lane run" case, so it checked the log of the wrong case. That case's own check_reran ("only the other lane's newest run decides") already covers the re-run there, so the old line was a duplicate that never checked the case its label names. expect truncates the log at the start of each case, and check_reran reads that log. With the move, each check_reran now follows the expect it describes, so the assertion labelled "a head with no other-lane run of its own" runs against the matching case. No coverage is lost.

I did not run the test script (no shell here). The PR's PASS=91 FAIL=0 figure is author-claimed and unverified by me.

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.

kyle-sexton and others added 5 commits October 23, 2026 11:31
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>
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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 525b215aabd3cea6129f7cedeb790918c1ba6ed0; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread .github/workflows/pr-feedback-dispatch.yml Outdated
kyle-sexton and others added 2 commits October 23, 2026 13:36
… 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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 540b163a6216d65615644298547b04b8b1ae5b41; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread scripts/ci-rerun-ci-status.test.sh Outdated
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>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 6cd8842 Oct 23, 2026
34 checks passed
@kyle-sexton
kyle-sexton deleted the perf/feedback-dispatch-fewer-ci-reruns branch October 23, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant