Repository navigation
perf(pr-merge): judge the gate's candidate on the run's listing at any age - #10558
Conversation
…y age The gate's own candidate is judged provisional on the run's saved artifact listing at any age: an allow is judged again on a listing of its own before the gate allows (refresh), and a FAIL, the FAIL cap or an old pull request stands, since no later upload clears them. Any other refusal is judged again on a listing under two minutes old, so none dequeues an entry on an older listing. Queued entries keep the two-minute bound. A candidate whose verdict allows and that the risk score then refuses lists nothing. A fixture of a pr-merge tick prints each job's babysit-gh-calls line and pins its listing count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hole The first tick on a carried base read 4 incremental pages that missed the live count and then paged all 71 in the skip filter; the same base read again minutes later held the count. One more read on the same base costs about 4 pages against 71, and each read is still checked against the live total_count. 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. critical-findings: 0 Both changed code files, Stale allow before the gate allows
Refusals on an older listing
Incremental re-read (
No trust control widens. Verifier-origin checks ( Spend: fewer GitHub API calls per pr-merge tick, plus one extra incremental read (about 4 pages) on a count miss. This is rate-limit spend on the GITHUB_TOKEN, not a billing change, and it is not a hold. |
|
claude-review reviewed Reviewed the whole diff (4 files) against the PR context. There is one advisory finding, below. I found no correctness problem in the provisional-judgment logic. Checked in the code
Verified: I only read the code. I did not run the tests. The test results, the live-run measurements and the fixture timings in the PR description are author-claimed and unverified. Finding: the retry loop gives each incremental read its own timeout. See the inline comment. No closing issue is named in the PR context ("No related issue"). The PR body contained no embedded instructions. critical-findings: 1 Findings not posted inline (their path, line or side is malformed):
To reproduce, make Fix: compute one deadline before the loop and pass it to each read, e.g. |
_incremental now answers COUNT_MISSED when its pages read whole but miss the live count, and None for a failed read. Only a count miss is read again on the same base; a timeout, a 429 or a rate-limit 403 pages the whole listing at once, as _read_listing's no-retry rule says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Post-merge mutation testingThe run's summary has the full report. |
No related issue: GITHUB_TOKEN spend follow-up to #10488
Summary
The pr-merge gate judges its own candidate's verifier verdict on the run's saved artifact listing at any age, instead of listing again whenever that listing is over two minutes old. A candidate that is refused, by its verdict or by anything after it such as the risk score, now lists nothing. Only a candidate the gate is about to allow, or a queued entry it keeps, still needs a recent listing.
This builds on #10488's carried, incremental listing and adds no listing path. The skip filter, the gate and clear-escalations already share one file per job (
BABYSIT_VERIFY_LISTING). The skip filter's listing is the job's first, built on the carry.Fix
One listing seam for pr-merge's verdict readers. pr-merge's verdict readers all read the repository artifact listing through
babysit_verify_evidence.Reader.listing()/_current_listing: the skip filter (babysit_merge_skip.py filter), each gate candidate, queued holds, enable-auto-merge and clear-escalations (evidence --shared-listing). The seam builds on the run's saved listing or perf(pr-merge): list only new artifacts on top of a trusted carried listing #10488's carry and pages the whole listing only as a fallback. This PR adds no second helper, and a future strategy goes behind_current_listing.Outside the seam, out of scope. Exact-name lookups of other artifacts remain elsewhere:
babysit_postmerge.py_lane_record(lane records);resolve-pr.mjsjudged;reconcile.mjspassStarts.INCREMENTAL_READS = 2: the incremental listing is read once more on the same base before the whole listing is paged.babysit_verify_evidence.py:Reader.evidence(..., provisional=True)judges on the listing at hand at any age.refresh()and judges the candidate again on a listing of its own before allowing it. That rule is unchanged.STANDINGrefusal (a FAIL at the head, the FAIL cap,verify:too-old) stands, because no later upload clears it.verify:missingnever dequeues an entry.Calls that are not provisional work as before: queued entries, enable-auto-merge's
--shared-listing, and theevidencecommand.babysit_merge.py: the candidate's first judgment passesprovisional=True.The trust rule is unchanged. Every artifact still counts only through
Reader._run_state/_origin(verifier origin:.github/workflows/pr-verify.yml, eventworkflow_run, on the branch, in this repository), and a FAIL still counts under any number and head.Cut 2 (exact-name verdict lookup before an allow): not built, on data
Coverage. The refresh before an allow exists to catch a FAIL uploaded since the listing, wherever
_evidencecounts one. Exact names find only the FAILs at this head (resolve-pr.mjsjudged). They miss:verify:earlier-fail);-i<issue>FAIL from another pull request (the cap);A name-only refresh would narrow the FAIL-anywhere rule.
Cost. The name set is this PR's pass, pass-notes and fail, each plain and with the digest, plus the
-iFAIL and the commit's other pull requests: 8 or more GETs and a GraphQL read. The incremental refresh it would replace reads 2 pages live (perf(pr-merge): list only new artifacts on top of a trusted carried listing #10488's measurement) and 5-6 in the fixture below. It would cost the same or more and cover less.Verification
Measured with a fixture of one pr-merge tick (
APrMergeTickListsOnlyBeforeAnAllowOrAKeep). Each job prints ababysit-gh-callsline.--shared-listing."Before" is origin/main at 23f7831, which includes #10488, so the table shows only this PR's increment. Most of the cut came from #10488: the coordinator measured babysit_merge.py's pages falling from 292 to 18 a tick. Do not compare the table with the pre-#10488 197 (merge) and 152 (enable-auto-merge) calls.
refresh(); it reads no extra listing pages."queued": []in every gate.--shared-listing), so it has nothing to cut here.Skip filter, live. It already reads the job's one listing on #10488's carry. The 71-page full listings came from two other causes:
INCREMENTAL_READS = 2). With it, the 23:17Z case costs about 4 more pages instead of 71.Does the skip filter earn its place? Measured over 44 scheduled merge jobs (05:50Z to 22:42Z on 2026-10-27, from job logs):
BABYSIT_VERIFY_LISTING), so its own cost is about 1 GraphQL point and nothing extra in listing pages.Tests:
plugins/source-control/skills/babysit-prs/scripts/tests: 3,265 passed serially (122 s). Under-n auto(xdist), the unrelated test_babysit_lane_room.py and test_babysit_postmerge_quarantine.py tests flake on git fixtures; they pass serially.saved, so the gate refreshes).Related
needs:edge that changes job timing.🤖 Generated with Claude Code