Repository navigation
feat(pr-merge): let a passed security review clear hold-enforcement paths, keep gate and trust paths held - #10298
Conversation
…aths Owner decision Q26 option B. The hold-path floor becomes one classed list per side (merge-holds.mjs HOLD_PATH_CLASSES, babysit_risk.FLOOR_CLASSES, pinned equal by test_merge_holds_sync.py). trust and gate globs still hold for a person (HOLD_PATH_FLOOR / HIGH_FLOOR). enforcement globs (the modules the floored scripts import and the Python that decides the holds, except babysit_merge*) score review-path:<glob> in the merge gate until the claude-security-review evidence binds to the head with no open finding; ci-status and enable-auto-merge's path check leave them to the gate. The rest of .github/scripts/pr-merge/ joins the floor as gate, and the review callers' hold-paths and ci-status's exempt-author hold_patterns name it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0249 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 Security review of #10298 at fe4abd8. I read all 1321 diff lines and the PR context. Trust control reviewed: merge-hold narrowing. The change moves 21 floored paths from a person-held I found no other issues. The following held up under review:
I did not verify two things because they sit outside the diff: which commit the verifier and gate modules run from, and the critical-findings: 0 (the one CRITICAL finding has an inline comment). No critical-findings: 1 Findings not posted inline (their path, line or side is malformed):
The skill lists these as merge-hold code, and narrowing them is CRITICAL when judged from the code alone. What now gets through. A pull request that edits one of these modules is cleared by one LLM lane's The LLM lane's input is the attacker's own diff and PR description, so it can be prompt-injected or can miss a subtle logic change. The lane's verdict is also the only barrier. ci-status no longer holds these paths either, and the PR's own residual 1 notes the window if a disarm fails. The code shows no mitigation, such as the person-held severity: CRITICAL |
|
claude-security-review holds this pull request for a person (critical-security) at The PR narrows the merge hold on the modules that enforce it. claude-lane: claude-security-review |
|
claude-review reviewed Code review: no findingsI read the full diff (1321 lines, 15 files) and the PR context. I ran no tests or commands, so every test result below is the author's claim and I have not verified it. What I checked
Observations, not counted as findings
Spend: nothing in this change adds a paid resource or billing setting, so there is no cost hold. Manifests: the diff changes no Closing issue: the PR links no closing issue ("No related issue: owner decision Q26 option B"), so there are no acceptance criteria to check. |
A lane PR that changes an enforcement floor path and a lane source is unbound for good at that head (lane-source-changed), so no lane can clear its review-path hold. Score it review-path-unbindable:<glob>, a person_reasons prefix, so it gets the human-gated label; a transient unbind stays the lane's review-path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
claude-security-review holds this pull request for a person (critical-security) at Trust-control change: the PR downgrades 21 merge-hold floor entries (babysit_risk.py, babysit_verify_evidence.py, babysit_review_evidence.py and the other babysit-prs modules, the pr-unblock and work-resolve-issue modules) from a person-held claude-lane: claude-security-review |
…code babysit_risk.py (security_review_clean) and babysit_review_evidence.py (bind) compute the condition that clears an enforcement path, so they stay a person's. Both classed lists and the sync test move them to gate; the pipeline README and config comments describe the classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
claude-security-review holds this pull request for a person (critical-security) at The PR weakens a merge hold, which the review rules treat as CRITICAL. It moves 19 floor paths (babysit_verify_evidence.py, babysit_state/util/gh/checks/classify and the other enforcement modules, the pr-unblock modules, work-resolve-issue/limits.mjs and report-outcome.mjs) from needing a person to needing only an LLM security-review pass with 0 open findings. ci-status no longer holds them (merge-holds.mjs HOLD_PATH_FLOOR filters out enforcement). A change to this code that the review misses merges unattended and then runs as the gate for later PRs, including those touching gate and trust paths. claude-lane: claude-security-review |
Class the floor files main added: rate-limits.json and pr-merge's verify-answers.mjs as gate (the rate guard's broker-loaded defaults and a merge-gate script), and the pr-unblock and work-resolve-issue retry-fetch.mjs copies as enforcement (modules the floored address-feedback.mjs and report-outcome.mjs import). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hold kept: owner stop reason 3 (a CRITICAL security hold). The owner decided Q-HOLDPATH2 = B on 2026-10-26 at 14:29Z, in the poteto session: "whatever is least friction, human involvement but safe". Under B, a passed security review bound to the current head clears hold-enforcement modules only. Merge-gate code, broker-loaded config and trust boundaries stay held for a person. claude-security-review CRITICAL-holds this PR because B narrows a merge hold (thread). Only the owner can lift that hold. Refreshed onto main at fe4abd8. The PR no longer waits on #10252. That refresh added two enforcement-class files to the 19 already listed, the two |
|
claude-security-review holds this pull request for a person (critical-security) at The change narrows merge holds. 21 floored paths move from a person hold to a clean claude-security-review clearance: babysit_verify_evidence.py and the babysit-prs modules it and babysit_risk.py import, plus the pr-unblock/ and work-resolve-issue/ modules that merge-holds.mjs and enable-auto-merge.mjs import (see .github/scripts/pr-merge/merge-holds.mjs HOLD_PATH_FLOOR and babysit_risk.py FLOOR_CLASSES). The skill treats narrowing a merge hold as a CRITICAL trust-control change. claude-lane: claude-security-review |
Main classed the hold-path floor (trust, gate, enforcement; #10298). This branch's floor additions (escalation-routes.mjs and .json, the scripts that act on a pr-unblock stop's route and their imports) join it as `enforcement`, beside the other pr-unblock modules, in both HOLD_PATH_CLASSES and FLOOR_CLASSES. The count pins follow the merged floor (62 entries, 32 enforcement). ci-status's exempt-author hold_patterns keeps every floor entry from both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: owner decision Q26 option B (Q-HOLDPATH2)
Summary
The owner chose Q26 option B: "whatever is least amount of friction, human involvement but safe". The pr-merge hold-path floor is now one classed list per language. Only the merge gate's own code, the config the token broker loads, and the trust boundaries still wait for a person. The other modules that enforce the floor merge once a clean security review is bound to the head.
trust(5).github/actions/check-trusted-trigger/**,.github/actions/check-kill-switch/**,.github/actions/request-lane-token/**,**/CODEOWNERS,**/.gitmodulesgate(23).github/actions/resolve-config/**(the broker runs it from main;effect-grants.jsonincluded),.github/actions/log-api-budget/rate-guard.mjs, every script in.github/scripts/pr-merge/(merge-holds,enable-auto-merge,superseded,clear-escalations, pluscarry-run-cache,gate-check,head-ref-map,lane-records,rate-stop.bash,refire-refused,rotation,sweep-summary,who,post-unarmed-status),babysit_merge.py,babysit_merge_policy.py,babysit_risk.pyandbabysit_review_evidence.py(they compute the security review's clear condition),.github/workflows/pr-require-checks.yml,.github/workflows/pr-merge.yml,docs/conventions/pr-pipeline.yaml(the broker readslanes:and each activity's effect from the tip)enforcement(19)babysit_verify_evidence,babysit_check_inputs,checks,classify,dependabot,feedback,gh,postmerge,repo_config,review_trigger,run_cache,state,util,whole_review(.py);.github/scripts/pr-unblock/{address-feedback,gate-source,thread-scope}.mjs;.github/scripts/work-resolve-issue/{report-outcome,limits}.mjsBasis for the broker-loaded set: ADR 0058 step 3, and the deny table and grant paragraph in
docs/conventions/pr-pipeline/pr-run-activity.md("The lane token broker"). The trusted-actor list stays under its own widening hold (actorsHold), which is unchanged.Passed security review.
babysit_risk.security_review_cleanrequires all of the following:claude-security-reviewevidence entry, from the existing binder (babysit_review_evidence.bind);boundset, with no unbound reason;open_findings == 0and a CRITICAL count of 0.The evidence counts open findings and CRITICAL findings, but not IMPORTANT findings on their own. Any open finding therefore holds, SUGGESTION included. That is the fail-closed reading of "no IMPORTANT finding open". A missing, doubled, unbound or malformed entry also holds. A clean
claude-reviewlane alone clears nothing.Fix
test_merge_holds_sync.py.HOLD_PATH_CLASSESin.github/scripts/pr-merge/merge-holds.mjs;HOLD_PATH_FLOOR(the person subset) andREVIEW_FLOORare derived from it.FLOOR_CLASSESinbabysit_risk.py;HIGH_FLOOR,REVIEW_FLOORandFLOORare derived from it.merge-holds.mjsalone from the base commit, and it imports nothing.FLOOR). Only their class changed..github/scripts/pr-merge/must be on the floor asgate.babysit_risk._high_path_reasons).enforcementpath scores HIGH asreview-path:<glob>unlesssecurity_review_clean.high-, soperson_reasons(from fix(pr-merge): route non-h38 needs-human holds to bounded retries and a failing status #10249) and enable-auto-merge treat it as a lane's refusal: a comment and no label.NEVER_FLOOR_EXEMPTkeeps every floor glob.pathHold,pathHoldKindanddecide()are unedited. They now read the person subset, so anenforcementpath reaches the gate's verifier read (evidence --lane-link --lane-reviews,enable-auto-merge.mjs:670), and that read scores it. Everyreviews: nullcomes from a completedscore()with nothing holding (babysit_verify_evidence.pylane_risk_refusal). A read that fails returnsunreadable: ..., which holds..github/workflows/change is a lane source (babysit_review_evidence.pyLANE_SOURCE_PREFIXES;bind()raiseslane-source-changed). A PR that also edits a review caller workflow therefore stays held. Tested end to end throughbind()andscore().hold-pathsand ci-status's exempt-authorhold_patternsgain the ten newgatepr-merge scripts. Nothing was removed.TheScrutinyListsNameTheFloorrequires every floor entry in each list. Group (ii) stays on all three lists.lane-source-changed). The path then scoresreview-path-unbindable:<glob>, ababysit_risk.PERSON_PREFIXESentry, soperson_reasonsnames it and enable-auto-merge labels it human-gated. Any other unbind (review not run yet, a failed read) stays the lane'sreview-path:. Tests:UnbindableReviewPathTests(2 of 4 fail-first; the 2 lane-side cases guard against widening). The 6 mutants are all killed: prefix dropped, any unbind treated as permanent, never unbindable, always unbindable, any lane's unbind counted, andreview-pathmade a person's.docs/conventions/pr-pipeline/README.mdfloor bullet and scrutiny paragraph, and thedocs/conventions/pr-pipeline.yamlfloor comment, now describe the classes. Changelog fragments for pr-pipeline and source-control.Verification
Fail-first:
ReviewClearedFloorTests(14 subtests) fail against main'sbabysit_risk.py.merge-holds.test.mjstests fail at import against main'smerge-holds.mjs.scripts/ci-lanes-pr-hold.test.shfail against main's module: thegate-check/whoholds and the six enforcement paths ci-status now leaves to the gate.Mutants, all killed by
test_babysit_risk+test_merge_holds_sync:open == 0, open-is-a-count,critical == 0..github/workflows/dropped fromLANE_SOURCE_PREFIXES.Suites at the head, which includes #10249 merged from main (re-run after the unbindable change: Python 3084 OK, node 483 pass,
ci-lanes-pr-hold.test.shPASS=157):node --test .github/scripts/pr-merge/*.test.mjs: 475 pass, 0 fail.unittest discover: 3080 tests, OK.scripts/ci-lanes-pr-hold.test.sh: PASS=157 FAIL=0.scripts/ci-review-lanes.test.sh: PASS=98.scripts/ci-review-plugin-source.test.sh: PASS=11.scripts/run-ruff.sh check: clean.scripts/check-line-citations.py: clean.check-affected: pass.Related
post-unarmed-status.mjs, which fix(pr-merge): route non-h38 needs-human holds to bounded retries and a failing status #10249 added, is on the floor asgate, andperson_reasonsdoes not countreview-path:(asserted).enforcementpaths. If a push lands after arming and the disarm mutation fails (it only warns,pr-require-checks.yml"A push gives an armed pull request..."), the merge queue checks a pr-verify pass at that head, not the security review. The scheduled reader disarms within 15 minutes. A CRITICAL security finding has the same window today.pr-run-activity-write.yml'srunjob holdsid-token: writeand runs head test files throughrun-test-file, so a test file is not inert there. Revisit once head tests no longer run alongside OIDC.🤖 Generated with Claude Code