Repository navigation
fix(pr-merge): require a pr-verify pass before a lane pull request merges natively - #9450
Conversation
…rges natively Native auto-merge armed lanes App pull requests on text holds alone, so one could merge with no verifier pass and past the repeated-FAIL cap. - enable-auto-merge.mjs arms, and keeps armed, only when pr-merge's own reader (babysit_verify_evidence.py, new `evidence` command) refuses nothing at the head it read: a pass stands, no FAIL at the head, FAILs under merge.max-verify-fails. The job reads it with GITHUB_TOKEN, the head-ref map and the carried run cache, as the gate does. - ci-status's lanes-hold step, in the merge queue only, requires a PASS or PASS-NOTES artifact at the queued head from a successful pr-verify run on main (merge-holds.mjs verdictHold), looked up by exact name. A trusted approval of the head lifts it. A pull request event reads nothing: pr-verify runs only after a green pull_request run, so a red ci-status there would never get a verdict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rkflow path to the resolver's Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ires-verdict # Conflicts: # .github/workflows/pr-require-checks.yml
…er env Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Event runs of enable-auto-merge look the head's own pass up by exact artifact name (2 to 4 lookups and a run read per pull request), the same lookup and rule as ci-status in the merge queue. Only the scheduled run spends babysit_verify_evidence.py, which adds the FAIL and repeated-FAIL refusals, and only it reads the head-ref map and run cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t' into fix/native-merge-requires-verdict
Review fixes. The merge-queue lookup and headPassVerify stopped at any successful run, so another workflow's artifact under one name could hide a real pass under a later name; both now stop only at a pass verdictHold counts. A hand-started run uses the full reader, an event run leaves an armed pull request armed instead of re-reading its verdict, and the reader stops starting new reads after seven minutes of the step's ten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner direction on the review findings: native auto-merge now agrees with the gate's full refusal list. Only the scheduled or hand-started enable-auto-merge run reads the verdict (babysit_verify_evidence.py) and arms; event runs read no verdict, never arm, and only disarm on a hold. ci-status turns an armed lanes App pull request's auto-merge off on a push to its head (one mutation, no read), so each new head waits for the reader. The merge-queue pass check stays as the backstop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…point 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 Incremental security review of The only change is one added shell comment line, No security findings. The PR context text was read as data and held no embedded directives. No manifest files are in this increment, so there are no critical-findings: 0 |
|
claude-review reviewed Incremental review of The change adds one comment line, I checked the marker against its consumer, The text accurately describes the step: it is a write, it isn't retried, and a failure only warns. This matches the PR description. No Everything else in the PR was reviewed at 7400f94 and is unchanged. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f4c1dfd848
ℹ️ 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".
…budget poteto's review and the lane review on f4c1dfd: - the evidence CLI judges a pass found on the run's saved listing again on a listing of its own, as babysit_merge.py does before it allows; - --base-sha resolves the FAIL cap through with_default_branch, the lower of the base's and main's, and enable-auto-merge passes the PR's base commit instead of main's config value; - a failed push disarm fails ci-status on that head instead of warning; - the queue check binds each run record to the run id its artifact names, and test_verify_rules_sync.py checks its lookup and origin rule against the gate's; - the reader stops starting reads at 5 minutes, so its 4-minute read limit ends inside the step's 10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stop poteto's review of 5bdd428: whether ci-status's GITHUB_TOKEN can call disablePullRequestAutoMerge is unconfirmed, and failing on it would hold every push to an armed lanes pull request red. A failed disarm now warns; the merge queue's pass-at-head check and the scheduled reader back it up. A new case fails when the lookup's early stop drops its run-id clause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's ci-gh-read check requires every plain gh api in ci-status to be a write it recognizes or carry the mark; a GraphQL mutation is a write it does not parse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: restores owner decisions Q567 and Q624 (required verifier verdict) after #9399
Summary
#9399 turned on native auto-merge for lanes App pull requests. Before arming,
enable-auto-merge.mjschecked onlyCloses #N, hold labels, body holds, vetoes, hold paths and a human disarm.ci-status, the only required check, did not read pr-verify either. Withmerge.report-only: false, a lanes App pull request could merge with no pr-verify PASS and past the repeated-FAIL cap.Owner decisions Q567 and Q624, and GUIDE rows 88, 96 and 117, keep a passing verdict from a non-author verifier, bound to the head commit, as a required condition of unattended merge. With this change, native auto-merge agrees with pr-merge's gate: it arms only when the gate's reader refuses nothing at the head.
Fix
enable-auto-merge (
.github/scripts/pr-merge/enable-auto-merge.mjs) arms a pull request only when pr-merge's own reader refuses nothing at the head it read. The reader isbabysit_verify_evidence.py, through a newevidencecommand. That means:merge.max-verify-failsheads, where the cap is the lower of the PR's base config and main's (--base-sha, throughwith_default_branch), as the gate resolves it;An armed pull request that fails any of these is disarmed, and unreadable evidence fails closed.
When the verdict is read. Only the scheduled run and a hand-started one read the verdict and arm (
READER_EVENTS). They read it last, only for pull requests every other rule would arm or keep armed. They share one artifact listing and one run cache, and they restore the head-ref map and carried run cache exactly as the gate job does.11,26,41,56).babysit_merge.pydoes before it allows.ci-status disarms on push. On a
synchronizeevent for an armed lanes App pull request, the lanes-hold step turns auto-merge off. It sends one GraphQL mutation with the job'sGITHUB_TOKENand makes no read: the REST pull request it already reads carriesauto_merge. A new head is armed again only after the reader judges it.GITHUB_TOKEN(contents: read,pull-requests: write) can calldisablePullRequestAutoMergeis UNVERIFIED. GitHub's docs name people with write access and PR authors, and peter-evans/enable-pull-request-automerge documentscontents: write. Follow-up issue: Confirm ci-status's GITHUB_TOKEN can disarm auto-merge on a lanes push, and test the queue lookup by behaviour #9471.ci-status in the merge queue (backstop). The same step requires a PASS or PASS-NOTES at the queued pull request's live head.
pr-verify-<pr>-<head>-passand-pass-notesby exact name, each with and without the head-branch digest. It stops at the first name holding a pass thatverdictHoldcounts.merge-holds.mjsverdictHoldchecks that the run record is the run the artifact names, and checks the run:pr-verify.yml, eventworkflow_run, branchmain, completed with success, and this repository as both repository and head repository. These are the gate's_originfields, without its compare read.Why no pull-request-event verdict check. pr-verify's
check-intentjob runs only when thepr-require-checksrun concludedsuccesson apull_requestevent (pr-verify.yml, thecheck-intentif:). A red ci-status for "no verdict yet" would stop pr-verify from ever judging the pull request. For the same reason no rerun wiring is added topr-feedback-dispatch.yml.Tokens. The
enable-auto-mergejob getsactions: readonGITHUB_TOKEN, which is how the gate job reads. The lane token's scopes are unchanged, and revert(pr-merge): drop merge-pr's lane token actions read #9438 keeps Actions off it.Docs.
docs/conventions/pr-pipeline/README.md, thepr-verify.ymlheader and the workflow comments are updated. Changelog fragments are added for pr-pipeline and source-control.API cost
pull_requestpushThe reader pass in the last row is one full artifact listing, shared across the run, plus the reader's per-PR reads, with the head-ref map and carried run cache. Today the listing is 4441 artifacts, about 45 pages. The gate job pages its own listing in the same run, so a scheduled run with an armable lanes PR pages it twice.
Measured reader cost:
evidenceon feat(harness-ops): add converge-plan.sh and confirm converge per project path #9418): 559 calls in about 9 minutes. The result wasrefusal: nullwith apass-notesat the head.That cost falls further once poteto's digest-named and issue-named writer (#9443 step B) lands, since a digest-named FAIL needs no download.
Follow-up
Verification
scripts/ci-lanes-pr-hold.test.sh: 97 pass. The gh stub runs the step's own--jqfilter over a listing that also holds an expired artifact and a suffixed name. I checked that removing theexpiredclause fails 12 cases. Cases:editedandlabeledevents, an unarmed PR, and a person's armed PR send none.pull_requestevent, another branch, a failed run;merge-holds.mjsthat ignores the verdicts fails closed.node --testin.github/scripts/pr-merge: 224 pass.spawnSyncpath, disarms. A passing one arms.verifyHoldandverdictHoldhave unit tables.VERIFY_PATHis tied to resolve-pr's and, through it, to the gate'sWORKFLOW_PATH.node --testin.github/scripts/pr-verify: 86 pass.unittestfortest_babysit_verify_evidence(newEvidenceCommandTest) andtest_verify_rules_sync: OK.scripts/run-ruff.sh check: clean.scripts/ci-*.test.shpasses exceptci-test-node-stop-commands.test.sh, which needs PyYAML. That is a local environment gap, and the same test fails on main.actionlintclean;shellcheckon the test clean;zizmoron pr-merge.yml and pr-require-checks.yml reports no findings;scripts/check-lane-coverage.sh --checkOK./pull-request ready; both review lanes readlocalin the venue check):workflow_dispatchrouting;_origin-parity comment.Related
GITHUB_TOKEN.🤖 Generated with Claude Code