Repository navigation
fix(source-control): re-read a short verdict listing and title verifier holds - #12233
Conversation
…ery artifact The pr-verify verdict reader sent an incremental listing that the pages near expiry left short straight to a whole listing of every repository artifact (134 to 293 pages). Two refusals on 2026-11-02 started that way: ci-status merge_group job 125052787044 ejected #12205 after its one whole listing's top-up came up short, and enable-auto-merge job 125053997338 paged three whole listings for #12203. A short count there is a page-boundary skip in the fresh pages, which a read after a wait restores, so it is now read again while the near-expiry caps left cover another such read. The whole listings' top-ups get those caps again, so the incremental reads cannot starve them; cold_cost counts the caps twice. merge-holds.mjs titles each verifier hold's error annotation `pr-verify hold`, so a reader of ci-status's annotations tells a verify:unreadable refusal from a body hold in the same step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… limit cannot wait Under a caller's limit (DEADLINE_ENV), the short-count retry after the pages near expiry runs only while the time left still fits that read, the longest wait and one whole listing; else the whole listing comes at once, as before. Adds the ci-status over-count-then-short shape as a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…title 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 Security review: PR #12233 at
|
|
claude-review reviewed Code review: #12233 (head e704fa1)I read every hunk of the 9 changed files. I also read the surrounding Reader change (
merge-holds change. Verifier holds now print as Finding (cross-file, on an unchanged line):
Not a finding.
Verification. All test results in the PR body are author-claimed and unverified. This lane has no shell and ran nothing. |
No related issue: incident fix for the verdict reader's artifact listing (#12205's merge-queue ejection, GITHUB_TOKEN breaker trips)
Summary
ci-status merge_group run 41374462603 (job 125052787044, #12205) spent core=562. The verdict reader made 134 whole-listing page calls (
repositories/{n}/actions/artifacts), 24 single-page calls (repos/{repo}/actions/artifacts) and 4 id reads. It then refusedverify:unreadable: repository artifacts: 1 whole listing never held GitHub's live artifact count, and the merge queue ejected #12205. pr-merge's enable-auto-merge job 125053997338 (run 41375133667) did the same for #12203: 293 whole-listing pages, 40 single pages and 2 id reads in one invocation.Both jobs'
babysit-listing-misslines show the same start:region_pages11region_pages0: refusedregion_pages11near-expiry-deadline: refusedIn both jobs, the near-expiry re-read dropped every entry GitHub had deleted early and left the count 1 to 3 short. The reader sent that short count straight to a whole listing of all ~13,500 artifacts (#12020: "a wait would re-read the same pages and spend the cap the top-up needs"). The re-read drops an id only on a 404 or an expired record, so a count it leaves short is a page-boundary skip in the 6 fresh pages (#10486). A read after a wait restores such a skip, as it already does for any other short incremental count.
The ci-status step that printed the refusal is named
Fail a lanes App pull request its body or a member holds, and its error annotation carried no category. A reader of the run therefore saw a body hold.Fix
Why not per-run reads (the proposed design). That design is rejected on evidence:
workflow_runruns on main. One gated read ofactions/workflows/pr-verify.yml/runs?per_page=3showshead_sha00f9939 (main's tip),head_branchmain anddisplay_title"pr-verify" on every run, withtotal_count15,449.?head_sha=<pr head>returns nothing, andpr-verify.ymlsets norun-name.babysit_verify_evidence.py's docstring states them:max_failscap (_elsewhere);pulls/<n>/commits, tied by the branch digest.workflows/pr-verify.yml/runs): +0 calls. Counter B (runs/{id}/artifacts): +0 calls. Neither read was added.Why the whole listing exists (history from git).
nameparameter matches exact names only ... exact names cannot find every FAIL the cross-PRmax_failscount needs." It also added the near-expiry re-read and "one whole listing per over-count".This change. Changes to
babysit_verify_evidence.py:COUNT_MISSEDinstead ofCOUNT_OVER. The count is then read again on the same base afterINCREMENTAL_WAITS. Each read faces the same exact-count and near-expiry check as before, so no read can allow a listing the old code refused.DEADLINE_ENV), the time left must still fit this read, the longest wait and one whole listing. Otherwise the whole listing comes at once, as before. In practice this allows one retry for an 11-page region under the 24-page cap._complete_listingresets the near-expiry caps, so the whole listings' top-ups get caps of their own. This answers fix(source-control): check near-expiry entries on an exact-count verdict listing #12020's starvation concern. Worst case:cold_costinpr-require-checks.ymlnow counts both caps twice. That is 1,681 → 1,739 at 7,522 artifacts and 2,079 → 2,137 at 10,450 artifacts. The comment,scripts/ci-lanes-pr-hold.test.shandTheColdReadHeadroomCoversTheListingCapsare updated to match.Kept unchanged: every FAIL rule, the trusted-producer checks (pr-verify on main, this repository, not forks), the near-expiry rules from #12020 and #11921, bounded loops (same
INCREMENTAL_READS, same caps per stage), failing closed on any unreadable read, and #11921's one whole listing per over-count.Defect 2 (the recorded category).
merge-holds.mjsnow writes every verifier hold (verdictHold,linkHold,evidenceHold) as::error title=pr-verify hold::#N: ...(VERIFIER_TITLE). Body holds, member vetoes and hold paths keep plain::error::. Body-hold behavior and exit codes are unchanged.Consumers of the step name were checked:
resolve-pr.mjsHOLD_STEP/heldOnly,reconcile.test.mjs,api-budget-coverage.test.mjsandci-lanes-pr-hold.test.sh. The step name is kept, so they keep working.heldOnlyjudges only pull_request runs, which carry no verifier lines.On a verifier-only hold, the module now also prints
#N: no body hold, no member veto and no hold path.and still exits 1. The workflow greps that line only after exit 0.events.log is the local hub's file, not part of this repository. To use the new category, its classifier must key on the annotation title. That is a follow-up for the hub owner.
Verification
test_babysit_verify_evidence.py: 424 passed, 356 subtests. Each test was run against origin/main's reader by swapping in only the module:full!=incremental).overcount-fallback, no.failed)An exact count that stays unsettled still refuses, pinned by
test_an_exact_count_the_pages_near_expiry_cannot_settle_refuses(cap 0).merge-holds.test.mjs: 66 pass. Against origin/main's module, the three title tests fail, including the new test "the reader's verify:unreadable is a pr-verify hold beside a body hold".scripts/ci-lanes-pr-hold.test.sh: PASS=300 FAIL=0.scripts/ci-hold-merge-group.test.sh: PASS=82.enable-auto-merge.test.mjs: 195 pass.resolve-pr.test.mjs: 143 pass.reconcile.test.mjs: 143 pass.api-budget-coverage.test.mjs: 4 pass.test_merge_holds_sync.py+test_verify_rules_sync.py: 39 pass.test_babysit_verify_answers.py,test_babysit_merge_skip.py,test_babysit_merge_unattended.py: 351 pass. Suites were run one at a time.check-lane-coverage.sh --check,check-pipefail-grep-q.sh,actionlintonpr-require-checks.yml,lint-shell-filter.test.sh(16 pass) and ruff: clean.evidencewas not run locally. It cannot reproduce a carry inside an early-deletion window, and it costs owner-token calls. After merge, verify with theapi-routesannotations of merge_group ci-status and enable-auto-merge jobs.Per-run call delta (R34), derived from the two jobs' miss lines (not re-run):
UNVERIFIED: whether those shortfalls were fresh-page skips or gaps in the carry. The miss lines do not tell the two apart. A carry is a listing saved after it held the live count, so a skip is the likelier cause.
Residual risks:
BABYSIT_VERIFY_NO_INCREMENTAL_WAITS=1on its fallback after a missed deadline, so that path reads once and gains nothing from the retry..failedfor the job, where it used to be transient. fix(source-control): stop the verdict reader's repeated cold artifact listings #11926's rule notes a full-budget timeout.This PR changes hold paths (
babysit_verify_evidence.py,merge-holds.mjs,pr-require-checks.yml), so a member's approval is required. It is not armed.Related
🤖 Generated with Claude Code