Repository navigation
feat(pr-pipeline): merge lanes App pull requests through native auto-merge - #9508
Conversation
enable-auto-merge no longer requires a body opening with Closes. The scheduled reader runs babysit_verify_evidence.py evidence --lane-link, which reads the issue link by babysit_merge_policy._linked_issue (one closing issue here, or one plain Refs: issue) before the verdict, and arms only when both allow. A refused or unreadable link reads no verdict and leaves the pull request unarmed or disarms it. Event runs read no link, so an armed Refs pull request is never disarmed on one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The job logged no api-budget line. Its arm step now names the lane and job tokens' readings for the reader's gh calls, and a job-end log-api-budget step, as the merge job has, reports both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d in-flight cap enable-auto-merge skipped the gate's run-level refusals, so after a demotion the next scheduled run re-armed clean lane pull requests. Each reader run now runs the post-merge check the merge job runs and reads the lane's room from it through babysit_merge_policy.lane_room. While the gate holds the lane off nothing is armed and armed lane pull requests are disarmed; at merge.max-in-flight nothing more is armed; an unreadable room arms nothing and disarms nothing. PULLS_QUERY now notes that it assumes main is the default branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ive link verdictHold accepted any pass at the head, so an armed Refs: #A pull request whose body was edited to Closes #B merged closing B on a pass that judged A. ci-status's merge-group step now reads the counted pass's verdict.json and the closing issues and holds unless the live link, by the gate's rule, is the issue the pass judged, linked the same way. refsIssue moves into merge-holds.mjs, which ci-status fetches alone, and resolve-pr.mjs re-exports it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The native path held a CRITICAL security finding only through the needs-human label, which the security lane applies only when its model fills the hold field. The gate holds on the counted finding itself. enable-auto-merge's reader now runs evidence --lane-reviews, which binds both lanes' evidence to the head as the gate does and applies the gate's lane rule, so an open CRITICAL finding or an unbound review leaves the pull request unarmed or disarms it. The README describes the new holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s timeouts The post-merge check gets its own 6-minute timeout, the arm step 14 minutes for the room read plus the verdict budget, and the job 30, with workflow.test.mjs checking that the step timeouts fit inside the job's. The verdict budget now starts after the room read. The pr-pipeline.yaml activity comment and the job header comment name the new holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Running the post-merge check a third time per scheduled run added about 350 GITHUB_TOKEN requests an hour against a 1,000 an hour limit. post-merge-dispatch now keeps its report as a one-day artifact, and enable-auto-merge needs that job and downloads the report from this run only. With no report the room is unreadable: nothing is armed and nothing is disarmed. Event runs, which skip post-merge-dispatch, run as before. The job drops issues: read and its timeout falls to 25 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m is unknown roomHold returned early for an armed pull request before it checked an unreadable room, so a missing post-merge report or a failed read left armed lane pull requests armed while the gate refused. Unknown holds: on a reader run an unreadable room now disarms every armed lane pull request and arms nothing, as a refusal does. Event runs read no room and are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e would not reuse lane_room read the demotion and post-merge refusal straight from the run's post-merge report. A re-run of enable-auto-merge downloads the run's first report, up to a day old, so a demotion filed since went unseen and lane pull requests armed. lane_room now applies the gate's own reuse checks (shared_refusal's age rules, now one helper, and _demotion_changed) and reads an answer that fails them as unreadable, which holds. The report is read whenever post-merge-dispatch ran, not only when it succeeded: its full-run steps can fail after the report is kept, and the gate does not refuse on them. The merge queue's relink hold no longer says pr-verify judges the new link: it skips a head it already passed, so a new head or an approval recovers it. 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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a553dfcdaa
ℹ️ 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".
|
claude-security-review reviewed No security issues found in the incremental change (since 2948665). Incremental scope. The change touches
Every refusal it returns becomes Fail-closed behaviour.
Instruction-surface and manifest items. The diff deletes no standing instruction, adds no manifest field, and touches no trust-gate path (trusted-actors, check-trusted-trigger, CODEOWNERS, Tests. The new test pins the PR context. It contains no embedded directives aimed at the reviewer. |
|
claude-review reviewed Incremental review of the changes since 2948665: the changelog fragment, the pr-pipeline README, What changed. What I checked by reading the code. I ran no tests or commands.
Docs wording, not reported. The changelog and README describe a report the gate would not reuse as "older than 30 minutes". Manifest fields. None: the diff touches no Closing issue. The PR states "No related issue", so there are no acceptance criteria to check. Hold. None. The files changed here are not in the cost or trust-gate classes. |
…d the kill switch The native path read only the review lanes' part of the gate's risk score, so a lane pull request adding a symlink or a submodule pointer, or touching a high path, a manifest, the trust gate or a cost path, armed where the gate holds it. evidence --lane-reviews now runs babysit_risk.score and holding over the inputs the gate builds, at the lane room's effective rung, and fails closed on any read error. ci-status's lanes App step now fails while the lanes kill switch is on, so an armed lane pull request stops in the merge queue; other authors are unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…records The lane's room re-checks the demotion records through the job's GITHUB_TOKEN; without issues: read that read fails, the room reads as unreadable, and no lane pull request is ever armed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lane_room checked the report's age and demotion changes but not whether a lane merge had landed since it was computed, so a merge whose full run had already failed did not stop native arming. It now applies the gate's own reuse rule, shared_refusal, as a candidate at the default branch head, and any refusal (new-merge:#<n> included) makes the room unreadable, which disarms and arms nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: completes the native merge path after #9450, #9459 and #9488
Summary
merge.lane-prsfromgatetonativeindocs/conventions/pr-pipeline.yaml. The merge gate stops listing lanes App pull requests; native auto-merge andci-statusbecome their whole merge path.enable-auto-mergearms a lane pull request whose issue link the gate takes, a plainRefs: #<n>issue included (owner decision on feat(source-control): read a Refs: issue as the linked issue for pr-merge eligibility #9405). The link is decided by the gate's own Python rule.merge.max-in-flight;enable-auto-mergejob now logs an api-budget line at job end (part of perf(ci): log api-budget lines from pr-verify, pr-test-windows, intake, work-unblock, post-merge and enable-auto-merge #9497).Fix
Link eligibility (
evidence --lane-link). Before the verdict, the reader reads the link bybabysit_merge_policy._linked_issue: one closing issue here, or, with none, the one issue plainRefs:lines name. A refused or unreadable link reads no verdict and leaves the pull request unarmed, or disarms it.Run-level refusals (
lane-room,babysit_merge_policy.lane_room). Each scheduled or hand-started run reads this run's post-merge-dispatch report. It then applies the gate's run-level refusals, in the gate's order:merge.rung, the loop setting, the rung cap (vars.CLAUDE_LANES_MERGE_RUNG_CAP, passed as the merge job passes it), or a demotion record, an unreadable one included.While the gate holds its lane off, nothing is armed and every armed lane pull request is disarmed. At
merge.max-in-flightnothing more is armed. The count is the gate's (the queue's lane entries plus the lane's unverified merges) plus armed pull requests the queue has not taken yet.When a read fails (a missing or unparsable post-merge report, config, or merge queue), the room is unknown, and unknown holds (owner Q677): nothing is armed and every armed lane pull request is disarmed, as on a refusal. Event runs read no room and never disarm on one.
Risk-score hold (
evidence --lane-reviews,lane_risk_refusal). After the verdict allows, the reader runs the gate'sbabysit_risk.scoreandholdingover the inputs the gate builds, at the lane room's rung. The review lanes' evidence is bound to the head as the gate binds it (babysit_merge._lane_evidence). Each of these holds:claude-review findings are reported only.
Link binding in the merge queue (
merge-holds.mjslinkHold).verdictHoldaccepted any pass at the head, so an armedRefs: #Apull request edited toCloses #Bmerged closing #B on a pass that judged #A. Now ci-status's merge-group step reads the counted pass'sverdict.json(one artifact download) and the closing issues (one GraphQL read). It holds unless the live link, by the gate's rule, is the issue the pass judged, linked the same way. This is the reader'sverify:spec-changedrule. A trusted approval of the head lifts it, as it lifts the verifier hold.refsIssuemoves intomerge-holds.mjs, which ci-status fetches as one file, andresolve-pr.mjsre-exports it, so the parity test withrefs_issuestill covers it.CRITICAL security hold (owner Q693). Before this, the native path held a CRITICAL finding only through the
needs-humanlabel. The chain:plugins/review/skills/security-review/SKILL.md:231(critical-security) tells the model when to hold;pr-review-cli.yml(v0.65.0) applies the label only when the model'sholdfield says so;pr-require-checks.yml:3712(human_gated) fails ci-status while the label is on;enable-auto-mergeholdReasonskips a labelled pull request.ci-workflows'
apply-review-holdhas no severity trigger of its own, so a missedholdlet a CRITICAL finding through. The gate counts the findings themselves, and--lane-reviewsnow does the same on the native path.Default branch. A comment at
PULLS_QUERYsays it assumes main is the default branch; the gate reads it live (_base_refusal).Permissions. The
enable-auto-mergejob gainspull-requests: readandchecks: read.lane-roomruns under the job'sGITHUB_TOKEN. The review-lane reads split tokens as the merge job's do: the timeline, comments and lane runs under the job'sGITHUB_TOKEN(BABYSIT_VERIFY_GH_TOKEN), and the config, changed-file and permission reads under the lane token (GH_TOKEN).API budget. The job-end step logs only the Python
ghcalls.createGitHubandhead-ref-map.mjsrecord nothing, so event runs log no line and the lanes-app figure leaves out the JS calls. The second commit's message says the step "reports both"; it reports both tokens' Python readings only.Cost (measured). On each scheduled run the job reads the lane's room from the post-merge report that the same run's post-merge-dispatch job keeps as a one-day artifact (
needs: post-merge-dispatch, downloaded with no run-id, so only this run's artifact is read). It runs no post-merge check of its own. With no report, nothing is armed and every armed lane pull request is disarmed.New calls per scheduled run:
lane-room: 9GITHUB_TOKENcalls (8 measured, plus the demotion-record check added at ready);--lane-reviews: 36 per pull request whose verdict already allows (measured on feat(source-control)!: ship the pr_open_state default as ready #9483), split between the two tokens as the gate's lane-evidence read is.The post-merge check adds none. Measured locally and read-only.
Event runs skip post-merge-dispatch and run as before; they never arm.
Timeouts. The arm step has 14 minutes (the room read, then the verdict budget). The job has 25, and
workflow.test.mjschecks that the step timeouts fit inside it. On the schedule the job now starts after post-merge-dispatch ends; that job took 97-127 s on recent runs (its own comment).Open question for the owner.
--remediatedequeues the lane's queue entries but leaves armed pull requests armed, so GitHub can enqueue one before the next reader run disarms it. The coordinator is filing that follow-up.Verification
node --test .github/scripts/pr-merge/*.test.mjs .github/scripts/pr-verify/*.test.mjs: 342 pass, 0 fail. New tests:pythonRoompasses the rung cap and the job token;linkHoldcases from the README "Eligibility" rule;workflow.test.mjs.scripts/ci-lanes-pr-hold.test.sh: 121 pass. The kill switch on (either variable, empty or any value butfalse) fails a lanes App pull request but not a person's or Dependabot's, and the step reads both switches fromvars; making the check inert fails 4 cases. New merge-group cases:Refs:pass passes;Refs: #1edited toCloses #2fails;Refs:lines or two closing issues hold;ci-hold-merge-group,ci-hold-cost-decisionandci-hold-lifted-bypass: 22, 72 and 33 cases.python3 -m unittest discover -s tests -p 'test_babysit_merge*.py': 945 OK.tests.test_babysit_lane_reviews,tests.test_babysit_lane_room,tests.test_babysit_verify_evidence,tests.test_verify_rules_sync,tests.test_merge_holds_syncandtests.test_babysit_merge_policy: 646 OK.effective_rungalways C2: 3 lane-room tests fail;roomHoldinert: 3 JS tests fail;linkHoldreturning null: 5 step-body cases fail.scripts/run-ruff.sh checkpasses, andactionlintis clean on both workflows.Nonefor open lanes App pull requests feat(source-control)!: ship the pr_open_state default as ready #9483 and feat(evals): close the eval suite's judge-calibration sample gaps #9479. For docs(prerequisites): name an existing skill in the example's check field #9487 it answeredevidence-unbound:claude-security-review: not bound: skip-not-no-change, which the gate holds as well.no-run. Merged pull requests are not candidates.lane_roomreads its config, default branch and cap through the run cache.carry-run-cache.mjs:87carries onlycarried-run-record,carried-fail-head-refandcarried-on-branch, so a poisoned cache entry cannot change the rung or the cap.lane-roomon this repository answered{"refusal": null, "unreadable": null, "count": 0, "cap": 5, "queued": [9510]}.enqueuer(docs(harness-ops): add harness-ops:prerequisites row to README skills table #9475'sAddedToMergeQueueEvent), so the in-flight count sees native merges.Security notes.
Refs:line. That only passes the link check: a pass must still bind withspec_kind: refsto a trusted spec, and in the queue it must still bind the live link.linkHoldfrom the base commit'smerge-holds.mjs, so a pull request cannot rewrite it.Review at ready
The local code and security reviews at the ready flip (no CI review lane ran on this repository) found these. Fixed in 62c6679:
lane_roomused the post-merge report without the gate's reuse checks. A re-run ofenable-auto-mergedownloads the run's first report, up to a day old, so a demotion filed since went unseen. It now appliesshared_refusal's age rules and_demotion_changed, and an answer that fails them reads as unreadable, which holds.post-merge-dispatchsucceeded. A failed full-run step after the report was kept disarmed every armed lane pull request, which the gate does not do. It is now read whenever that job ran.Fixed in 10c0940 (owner-approved parity with the gate):
Risk-score parity.
evidence --lane-reviewsnow runs the gate's wholebabysit_risk.score()andholding()over the inputs the gate builds for a lane pull request (score_unattended). It scores at the lane room's effective rung (now inlane-room's report), uses the base config tightened by main's, and passes the cost decision on the linked issue. So these holdClosesandRefs:pull requests alike:_mode_problems);Any read error holds. Cost: 38
ghcalls per pull request whose verdict already allows, measured read-only on feat(source-control)!: ship the pr_open_state default as ready #9483 including its pull-request and config reads. That is up from 36 for the lane-evidence read alone: one base-tree read for Pulumi directories and the manifest contents. It is spent only on pull requests every other rule would arm or keep armed.Kill switch. ci-status's lanes App step fails while
CLAUDE_LANES_DISABLEDorCLAUDE_LANES_REPO_DISABLEDreads anything butfalse, the check-kill-switch rule. An armed lane pull request therefore stops in the merge queue. It reads thevarscontext: no API call. Owner and Dependabot pull requests exit before the check.Recorded for the owner, not changed here:
Related
Refs:eligibility at the gate, the rule this reuses🤖 Generated with Claude Code