Repository navigation
perf(source-control): cut pr-merge gate reads per candidate - #9629
Conversation
The post-merge check compares the local clone's copy of the branch with a moved live tip once, so lane records on that copy need no compare each. The unattended gate stops a closed or merged pull request right after its REST read when no state directory can hold a merge request read-back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stop a merged pull request after the identity and queue readers instead of right after the REST read, so its gate still dequeues a held entry when it is the run's last candidate. Only a merge stops early, since a closed pull request can reopen before the gate's own state read. Spend no bridge compare when the clone already holds the live tip. 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 issues found in this pull request (head 14da5c5). The change touches the babysit-prs merge gate, so I checked both changes for ways they could loosen a merge decision.
|
|
claude-review reviewed Code review: no findingsI read the full diff ( Compare-storm fix (
Early stop (
Closing issue: none. The PR context says "No related issue", so there are no acceptance criteria to check. Manifests: no Hold: none. The PR adds no model or API spend, trigger or schedule, and touches no trust-gate path. It touches no Not flagged:
|
No related issue: pr-merge gate slowdown in scheduled run 40072153191 (per-candidate deadline hit, 4 of 6 candidates judged)
Summary
In scheduled pr-merge run 40072153191, main moved at 07:06:05Z while the gate was running. From then on, every
_lane_sendercheck compared a lane record's run head against a live tip the.baseclone did not have. Thebabysit-gh-callslines in that run's log show two candidates at 190 and 211 gh calls in total, 146 and 162 of them compares. Each call took about 1.25 s, so two candidates hit the 240 s per-candidate deadline and only 4 of 6 were judged inside the 10-minute budget. One of the two, #9618, had already merged, yet it still ran the full post-merge check before the gate's not-open blocker.Compare calls per candidate, measured in the new tests (20 records on the old tip, one between the old and the new tip, one off the branch):
On real runs, "after" is one bridge compare per candidate process, because each candidate reads the tip once. It is not one per run.
Fix
babysit_postmerge.py). Whencontains(ancestor, tip)finds that the clone lacks the live branch tip,_tip_bridgereads the clone's copy of the branch (refs/remotes/origin/<branch>, elserefs/heads/<branch>) and compares that copy with the tip once per tip.aheadoridentical, any record whose head is a local ancestor of that copy is an ancestor of the tip, with no further call.PostMergeReadError. A failed bridge compare does not raise; it only turns the bridge off, because the per-record compare it falls back to fails closed on its own..basecheckouts setpersist-credentials: false(.github/workflows/pr-merge.yml),_history_checkoutsalso returns the user's working clone (a fetch would modify it), andHISTORY_GIT_ENVsetsGIT_NO_LAZY_FETCH, so these history reads never touch the network.babysit_merge_policy.assess). With the newstop_when_closedflag, a pull request whose REST record readsstate: closed, merged: truerefuses once the identity, credential, kill-switch, default-branch and permission reads are done, and before the demotion, floor and post-merge reads. The refusal reuses the gate's own blocker text, now innot_open_refusal:state=MERGED (not OPEN). A closed pull request that was not merged is read in full, because it can reopen before the gate's own GraphQL state read, and stopping it then would refuse a pull request the full read could pass.--state-dir, which is how the pr-merge lane runs. A state directory can hold a recorded merge request whose read-back for a merged pull request dispatches the full run and needs the whole policy, so with one the old path runs.STOP_REFUSALSprefix, so it dequeues nothing.unattended_writesqueue sweep still runs for that candidate, because the stop comes after the readers it needs. The prefilter keeps one candidate alive so that some gate sweeps the queue (babysit_merge_prefilter.py), and that candidate may be the merged one.Verification
TipMovedintest_babysit_postmerge_contains.pycounted 22 compares where 3 were expected.test_a_merged_pr_with_no_state_dir_skips_the_post_merge_checkintest_babysit_merge_unattended.pyfound the post-merge check called once.Assesstests intest_babysit_merge_policy.pyhit aTypeErroron the new flag.--merge, and a closed unmerged one is read in full.uv run --no-project --with pytest python -I -m pytest -q plugins/source-control/skills/babysit-prs/scripts/testsgave 2834 passed.bash scripts/run-ruff.sh formatandcheckpass on the changed files.Related
🤖 Generated with Claude Code