Repository navigation
perf(pr-merge): skip unchanged lane refusals, scope event runs, one candidate listing - #11094
Conversation
…l request, files in the discussion read Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uto-merge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hand skip records on without answers 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 the incremental change ( What changed: Why it doesn't weaken a gate:
Tests: the new Spend: none. |
|
claude-review reviewed Incremental review of The change: Checks I made by reading the code:
I did not run the tests; this is a read-only review. There is no hold, no manifest change, and no cost impact. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 220f137d2f
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
readAnswers returned undefined for every download error, so a failed `gh run download` read as "no answers file" and raised no warning. Only a file missing from the artifact (ENOENT on open) now means no answers; any other error reaches main's warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
claude-review finding on 220f137 (the readAnswers download error is swallowed): VALID, fixed in 8e83e8d. |
No related issue: enable-auto-merge batch and pr-merge GITHUB_TOKEN audit items 2 and 5
Summary
Cuts pr-merge's GitHub reads, from GitHub Support's read-only GITHUB_TOKEN audit (items 2, 5 and 7), and records a keep or drop verdict for each item of the enable-auto-merge batch plan against current main. Since that plan was written, #11058 (182cde0,
merge.diff-check.path-holds: false) turned off every path hold.This PR builds:
pull_request_targetrun handles only its own pull request.gh pr listinstead of one per author.Audit item 3 and plan items 1-4 are not built. The table below gives each verdict and its basis.
Verdicts
liftBlockercounts the lanes App's stale-review-hold removalclear-escalations.mjs:182definesclearedMarker(reason, head), but nostale-review-holdreason exists (grep of.github/scripts/pr-merge/*.mjsis empty), because #10959 is not merged. The item also serves only approvalLift's lift of a hold-path refusal (enable-auto-merge.mjsapprovalLift, which requiresholdPathOnly), and §10 removes path holds.clear-escalations.mjs:772foreignMarker(appComments, since, known)). Labels pr-unblock clears on its own would then wait for a person, and every reader run adds a discussion read and a verifier read. Both work against §10 and against this PR's goal.review-path:reasons. Withpath-holds: false(docs/conventions/pr-pipeline.yaml:1101),babysit_risk.py:2266-2268moves everyreview-path:reason toskipped(is_path_hold, :2283). The comment text atbabysit_verify_evidence.py:2512is built fromheld, which never draws onskipped. The duplicated remedy can no longer reach a comment in this repository. The brief named (a) as a keep; this verdict overrides it on that code basis.personReasons,holdPathOnly,refusalWrites, clear-escalationsnativeRefusal,prMergeEscalationsandlaneCause, and the ci-status jq all invert. The plan's parent decision (plan, "Parent decisions": "PR 2, separate: item 4") keeps that format change out of a read-cut PR.DETAIL_QUERYpr-merge.yml:1392and:2210each hand the readerBABYSIT_VERIFY_LISTING. Each job already lists incrementally from the carried listing (babysit_verify_evidence.py:1089-1100, pages newer than the base only, about 1 page a tick). A shared listing goes stale afterbabysit_run_cache.py:31MAX_AGE = 2 minutes, and enable-auto-merge reads verdicts for up to 5 minutes (VERIFY_BUDGET_MS). The jobs would mostly list again anyway, so it saves about 1 GITHUB_TOKEN core call a tick (estimate) at the price of a cross-job hand-off.Fix
One candidate listing (audit item 7). The merge job makes one
gh pr list --base main --state open --limit 1000 --json ...,authorcall and keeps each listed author's pull requests with jq. Login matching ignores case, and gh names an Appapp/<slug>, as the earlier--authorarguments did. A list of 1000 may be cut short, so it stops the job red. With no author at all, it calls nothing, as before.Event runs handle one pull request (audit item 5). Both jobs take
EVENT_PR: ${{ github.event.pull_request.number }}throughenv:. The merge job keeps only that number from its API listing; enable-auto-merge does the same withlistPulls's answer. The payload never builds a candidate, and the schedule and a hand-started run keep the full sweep. A pendingpull_request_targetrun no longer supersedes another one (superseded.mjseligible), because each now does different work. Run artifacts from event runs are never read back (rotation.mjs:57accepts only schedule and workflow_dispatch runs), so a one-PR run cannot reset the cursor or the skip records.Files in the discussion read (audit item 2).
DETAIL_QUERYnow listsfiles(first: 100) { totalCount nodes { path changeType } }.listedFilesuses that list when it is complete, holds fewer than 100 entries and contains noRENAMEDorCOPIEDchange (only REST'sprevious_filenamenames the old path). Otherwise the REST pages are read as before.Skip-unchanged in enable-auto-merge (audit item 2). This mirrors
babysit_merge_skip.py. A reader run keeps, for each unarmed lanes App pull request it left on a lane refusal inSKIPPABLE(a lane risk-score hold, or a pr-unblock stop) and wrote nothing for, a key covering:updatedAtand labels;babysit_merge_skip.pr_statekeys them;pythonVerify'srung, plusRUNG_CAP).These come from one aliased GraphQL query per 20 pull requests, since a check, a status, a resolved thread or an edited comment need not move
updatedAt. The next reader run leaves a pull request whose key still matches on that refusal, with no discussion, files, statuses or verifier read.A skip never arms, and none happens:
merge.skip-unchangedmax-minutes (60) or max-skips (3), which reuse the merge gate's settings;pr-merge/unarmedstatus.A verdict refusal (
verify:*) is never kept, because a new pr-verify artifact changes nothing in the key. Neither is a whole-review request, an ejection comment, or any run that wrote a note.The records ride in the existing
pr-merge-verify-answersartifact aseam-skip.json.verify-answers.mjsreads them from the download it already makes (ghDownloadnow extracts once per run id), so the hand-off adds no call. It reads them before the answers file, so a run whose reader read nothing (every pull request skipped or held, so no answers file) still hands its records on. A record from another repository, or one dated in the future, malformed or expired, skips nothing.Metrics
All figures are estimates (UNVERIFIED) until the after measurement. Each names its token pool.
DETAIL_QUERY(GraphQL). With 2-5 of the 8 open lanes App PRs unlabeled and unchanged, that is 2-5 times (1 + V) GITHUB_TOKEN core calls, where V is the PR'sgithub-token=on its lastenable-auto-merge: #N calls=...cost line. The key costs 1 GraphQL query per 20 PRs (no core call).enable-auto-merge: skip-unchanged skipped=N recorded=M keyed=K of L, plus one#N left, unchanged since <time> (skip k of 3)line per skip/pulls/<n>/filesGETs in the job's API budget logbabysit_merge_skip.pydocstring), and in enable-auto-merge one discussion, files and statuses read for each other armed lanes PRsweep-cost=... candidates=Nline on apull_request_targetrun (N=1 or 0)pull_request_targetruns that reach the gate steplane-prs: native: 11 trusted actors other than the lanes App, plus Dependabot;trusted-actors.jsonlists 13 logins), on every tick and every eventgh pr list --authorruns a search query)gh pr listcount in the merge job's API budget logVerification
enable-auto-merge.test.mjs:updatedAt, is read again;updatedAt, is read again;merge-job.test.mjs: the listing tests rewritten to one list; an event run lists only its own pull request; a list that may be cut short stops red.superseded.test.mjs: an event run supersedes no other event run.verify-answers.test.mjs: skip records beside the answers, none left planted; a run that kept only skip records still hands them on, with no warning.node --testoverenable-auto-merge,merge-job,workflow,superseded,verify-answers,rotation,listing-carry,head-ref-map,carry-run-cache,lane-recordsandclear-escalations: 557 pass, 0 fail.scripts/check-changelog-fragments.sh --checkand--check-required origin/main: pass, with fragment.changes/pr-pipeline/perf-enable-auto-merge-batch-7c4e2a91.md.actionlint .github/workflows/pr-merge.yml: clean.zizmor --offline .github/workflows/pr-merge.yml: no findings.Related
merge.diff-check.path-holds), which this re-check is against.babysit_merge.py,babysit_gh.py,babysit_review_evidence.py,pr-review-main.yml.🤖 Generated with Claude Code