Repository navigation
fix(pr-merge): route non-h38 needs-human holds to bounded retries and a failing status - #10249
Conversation
… attempt The merge gate's rate-limit re-run keyed on the lane job's conclusion, but the review binding takes attempt 2 only after an attempt 1 that concluded failure (review_evidence.rerun_binds). The gate now reads attempt 1 and re-runs only when it concluded failure, so its trigger set stays a subset of the binding set (#10188 follow-up S2). Also pins the lane room's demotion keep to every Demotion field with two subtests (another clear, another newest record) and states that in shared_refusal's docstring (#10162 follow-ups S1, S2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a failing status Owner rule 2026-10-26: a person is pulled in only for a change in intent, a finding that alters intent or primary design, or a HIGH or CRITICAL security issue; cost, trust and protected-path holds stay. - babysit_risk.person_reasons (PERSON_CODES) is the one list of risk reasons only a person clears. The gate escalates a risk hold only for those, and `evidence --lane-reviews` reports reviewsPerson, which enable-auto-merge.mjs reads in place of its own regex. - babysit_verify_evidence.NEEDS_HUMAN keeps only verify:untrusted-spec. The repeated-FAIL cap keeps its quoting comment on the gate path, with no label; enable-auto-merge reads the gate's needsHuman in place of its own list. - An issue's labels that did not read and changed files that could not all be read are a lane's refusal: comment, no label, read again. - A lane's refusal standing merge.risk-escalation-minutes at the head is handed to the new unarmed-status job, which posts a failing pr-merge/unarmed commit status (a passing one on arm) with its own token (contents: read, statuses: write, no lane token). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ermission 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. |
|
Hold (do-not-merge): this PR adds |
|
claude-review reviewed Code review: PR #10249 (h38 needs-human routing)I found nothing to block or flag. I read the full diff ( What I checked by reading the code
Security scrutiny of the trusted paths (
Prepaid spend, not a hold and not counted: the new Closing issue: none (the PR states "No related issue"), so there are no acceptance criteria to check. The PR body contains no instructions aimed at the reviewer. Manifests: no Cosmetic only: |
|
claude-security-review reviewed Security review: no findingscritical-findings: 0 The diff could not be read through Several changed files are on the trusted-path and trust-control lists: Does it weaken a merge hold?No. The PR changes who clears a refusal (a person, via the human-gated label, or the lanes). It does not change whether the pull request is refused.
New
|
…tatus per head Automation Lanes' review of #10249 at 14ff26f: - C1a, C1b: verify:repeated-fail (terminal) and verify:too-old (judged from creation) stay in NEEDS_HUMAN; only verify:spec-changed moves to the lanes. The gate's lane-only repeated-FAIL comment is gone. - C1c: a changed-file listing past the page cap never reads whole, so its hold and cost paths are never checked: a person's. EAM returns FILES_CAPPED_REASON; the gate scores files-capped (HIGH, PERSON_CODES) past the files API's 3000-file cap. - C1d: the security lane's blocking findings hold as security-finding whether or not its evidence binds. - C2: workflow.test.mjs pins the unarmed-status job's `if`. - I3: the unarmed-status timer runs from the first lane refusal at the head (a pr-merge-unarmed-since line kept while the head is unchanged), so a volatile reason does not restart it. - S1: issue-labels-unreadable (more than 100 labels) stays a person's. - S2: a refusal that becomes a person's withdraws a failing status. - S3: merge-holds.pathHoldKind returns a structured kind. - S4: the risk-routing test reads reviewsPerson from the Python. - S5: the status job trusts only the GitHub Actions bot's statuses. Comments promising HIGH or CRITICAL now say CRITICAL, matching the code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…outing # Conflicts: # .github/scripts/pr-merge/enable-auto-merge.mjs
|
Decision (owner, 2026-10-26): Q-STATUS yes. The |
…lear stale statuses Automation Lanes' re-review of #10249 at 8069b12: - The binder now carries the security lane's blocking count from the newest head of the pull request its evidence binds at, the pinned head first, when the lane unbinds (a new head, a newer marker, a retargeted base), so an open CRITICAL finding still reaches person_reasons as security-finding. It reads only what the caller already read. - A lane refusal noted at the head that no longer stands while another read hold leaves the pull request unarmed withdraws its failing pr-merge/unarmed status. - Exactly 300 changed files reads one more page and arms; only a non-empty fourth page is the person's FILES_CAPPED_REASON. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gate's head-file reader cached only successes, and _line_changed swallows the error, so a failed contents or compare read, a rate limit included, was sent again for every thread and for the carried security pass. The reader now latches its first failure: every later call raises without a read. Also dates the newer-marker subtest's S2 marker after S's, so the carried pass's older-marker read is exercised, and pins _require_marker's newest_only=False read directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and latch only repeatable reads Review threads on #10249 at 59df9a3: - enable-auto-merge withdraws a lane's failing pr-merge/unarmed status only on a decided outcome (a veto, a person turning auto-merge off, an arm, an escalation to a person). A run that read nothing new (verify budget spent, hold paths unread, a verdict read that threw) leaves it, so it no longer flaps. The verify deadline uses the run's clock. - The gate's head-file reader latches only a failed contents or comparison read; HeadFileUnproven, a verdict about one path, latches nothing, so other paths still read and their threads can clear. - Pins that the repeated-FAIL cap fails closed (an unread listing refuses; own FAILs cannot expire before the too-old refusal) and that size reasons never hold; the lane reasons comment no longer implies the lanes clear size reasons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…outing # Conflicts: # .github/scripts/pr-merge/enable-auto-merge.mjs # .github/scripts/pr-merge/workflow.test.mjs
|
The pr-unblock lane stopped on this pull request and needs a person: the merge stages a change under .github/workflows/, which the lane does not push.
To resume, deal with the reason above, remove the gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=10249 -f activity=resolve-conflicts |
Brings in #10249 (needs-human holds routed to bounded retries); merged cleanly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: owner rule 2026-10-26 needs-human routing, audit .work/poteto-review/needs-human-audit.md
Summary
Owner rule 2026-10-26 (h38): a person is pulled in (
needs-human) only for (1) a change in original intent, (2) a finding that alters intent or primary design, or (3) a HIGH or CRITICAL security issue. Cost holds (D1), trust and provenance gates (D2) and protected-path refusals (D6) stay. This PR changes the babysit merge gate,.github/scripts/pr-merge/*andpr-merge.yml. Each other needs-human writer there now leaves the pull request unarmed with its comment. The next scheduled runs judge it again. Once the comment has stood unchanged at the head formerge.risk-escalation-minutes, the head gets a failingpr-merge/unarmedcommit status.evidence-unbound, a listing a read did not finish, other lane-clearable risk reasonsenable-auto-merge.mjsused its own regex (PERSON_RISK); the gate labeled any holding reason that stood 60 minutesbabysit_risk.person_reasons(PERSON_CODES) is the one list;evidence --lane-reviewsemitsreviewsPersonand both paths read it. A lane reason gets the comment, no label, and on the native path a failingpr-merge/unarmedstatus after it stands. Partial: the bounded review re-request is not built (see Left out).trust-gate:*,high-path:**,high-path:<pattern>, costhigh-path:<pattern>moved from lane to person in the risk branch.pathHoldalready labels the same configured or floor path, and the gate path has nopathHold, so this adds stops and removes none.manifest-review-requiredpathHoldread failures (a page that did not read, nopath.matchesGlob)merge-holds.pathHoldKindreturns the kind, so the routing parses no prose (S3).issue-labels-unreadableunreadable: <exc>riskreviewsPerson: false)verify:repeated-failverify:too-oldverify:spec-changedverify:untrusted-specNEEDS_HUMAN= repeated-fail, too-old, untrusted-spec;enable-auto-mergereads the gate'sneedsHuman)costHoldnever ranFILES_CAPPED_REASONwhen a fourth page is not empty (exactly 300 files reads one more page and arms); Pythonfiles-capped(HIGH, inPERSON_CODES) past GitHub's 3000-file cap_carried_blocking). It tries the pinned head first and accepts an older marker naming that head. It uses only runs, comments and threads already read. The head-file reader (babysit_merge._head_file) latches its first failure, a rate limit included, so neither another thread nor the carried pass sends that read again. A read failure in_lane_evidence(no data at all) still carries nothing.pr-merge-unarmed-since head=… at=…line, kept while the head is unchanged)Hold:lineshared_refusaldocstring says the keep requires every fieldfailurefailure, so the trigger set stays a subset of the binding setenable-auto-merge.mjsheader (:89-105)Fix
babysit_risk.person_reasons/PERSON_CODES: a CRITICAL security finding (now also when the security lane is unbound, C1d), a cost hold,files-capped(a listing pastmerge_policy.FILES_API_CAP, C1c),trust-gate:*and everyhigh-path:*.babysit_verify_evidence.lane_risk_refusalreturns(text, person).evidenceemitsreviewsPersonbesidereviews, false for every unread answer.babysit_verify_evidence.NEEDS_HUMAN = (REPEATED_FAIL, TOO_OLD, UNTRUSTED_SPEC): onlyverify:spec-changedmoves to the lanes.babysit_merge.unattended_writes:person_reasons(holding(risk))is non-empty;babysit_merge.rerun_rate_limited_reviewreadsactions/runs/{id}/attempts/1. It re-runs only when that attempt concludedfailure.enable-auto-merge.mjs:verifyClearsreadsneedsHumanandreviewsPerson. An answer missing a flag counts as a person's.PERSON_REFUSALSand thePERSON_RISKregex are removed.TRUST_REFUSALSkeeps all three trust reasons,issue-labels-unreadableincluded.FILES_CAPPED_REASON); otherwisemerge-holds.pathHoldKind'skinddecides:hold-pathis a person's,unreada lane's.<!-- pr-merge-unarmed-since head=<sha> at=<time> -->, is kept while the head is unchanged.unarmedStatushands each lane refusal standingRISK_ESCALATION_MINUTESfrom that time toGITHUB_OUTPUT(unarmed-statuses). An arm, or an escalation to a person, hands on asuccess.verifyClears,verifyHold,refusalBodyandREFUSAL_MARKERkeep their exports and shapes for fix(pr-merge): clear a native gate's needs-human label once its cause is gone #10198.post-unarmed-status.mjsand the newunarmed-statusjob inpr-merge.yml:contents: readandstatuses: writeon the job's own token. The job holds no lane token or OIDC token and runs only main's script. This follows the pr-unblockreport-conflict-statusand work-resolve-issue precedent.successis written only over a status already there.pr-pipeline.yamlcomment forrisk-escalation-minutes, the pr-pipeline README, and the module docstrings. Changelog fragments for source-control and pr-pipeline.Permission addition for the owner: a new
statuses: writegrant on theunarmed-statusjob'sGITHUB_TOKEN. Nothing reads the status to decide a merge. Checked:ci-lanesand Actions-app check runs (fix-ci-dispatch.mjsheadChecks,lanesStatus).statusevents.ci-statusonly posts its own status.Verification
Fail-first, each run against the old logic before the fix:
enable-auto-merge.test.mjs: against the previous head's EAM, with only the new export stubbed, 11 of 115 fail. These include the page-cap listing, the since-timed unarmed status (a volatile reason, another head, the escalation's success), the verifier routing, the trust sync and the lane comment's since line. After the fix, all 115 pass.test_babysit_risk.PersonReasonsTests: against the previous head, there are 3 failures and 1 error. They cover the carried security finding on an unbound lane,files-cappedin the person set, and the cap test.test_babysit_review_evidence.CarriedBlockingTestsgoes through the real binder. A CRITICAL bound at S, then a retargeted base, or a push to S2 (no review yet, or a failed review with a newer marker), still yieldssecurity-finding. A SUGGESTION carries nothing. Against the previous binder there are 3 failures. Thenewest_only=Truemutant in the carried call fails the newer-marker subtest, whose S2 marker is now dated later than S's.test_an_older_marker_naming_a_head_is_read_only_off_the_newestpins_require_marker's older-marker read.test_the_cap_fails_closed_when_its_fails_cannot_be_readandtest_its_own_fails_cannot_expire_before_it_is_too_oldpin the repeated-FAIL cap (pinning tests; no code change needed).test_a_size_reason_holds_nothing_so_no_one_is_neededpins that size reasons never hold.an unread outcome withdraws no failing pr-merge/unarmed statuscovers three cases; each fails alone against the old withdraw rule.test_a_path_the_comparison_cannot_prove_latches_nothingfails with the old latch-on-anything rule.HeadFileFailure: after a rate-limited head-file read, a later read for another path sends nothing. A bind whose claude-review pass and carried security pass each need a head read sends exactly one. Against the previous_head_file, 2 tests fail.test_babysit_verify_evidence.test_a_changed_spec_alone_goes_back_to_the_lanes: it pins repeated-fail, too-old and untrusted-spec as a person's and spec-changed as the lanes'. Either earlierNEEDS_HUMANfails it.workflow.test.mjs: theif: falsemutant onunarmed-statusnow fails 1 test (C2).post-unarmed-status.test.mjs: dropping the creator check fails the forged-status test (S5).test_babysit_merge_unattended(labels_only_a_person_hold, MED subtest): forcing the old escalate-any-reason condition fails it.test_babysit_merge_rate_limit_rerun.test_a_failed_job_in_an_attempt_that_did_not_fail_is_not_rerun: fails 2 subtests before the fix.test_babysit_lane_room"another clear" and "another newest record": a records-only demotion compare fails both.Suites (local, 2026-10-26):
node --test .github/scripts/pr-merge/*.test.mjs: 464 pass, 0 fail (after merging main, fix(pr-merge): clear a native gate's needs-human label once its cause is gone #10198 included).scripts/ci-lanes-pr-hold.test.sh: PASS=154 FAIL=0.scripts/run-ruff.sh check) is clean.scripts/check-changelog-fragments.sh --check-required origin/main: OK.scripts/check-affected.sh --tests: 13 passed, 1 skipped (no changed shell) over 23 changed files.Left out, each needing an owner decision or another lane:
Review re-request (P1a), pr-verify re-dispatch (P5c), issue-lane re-run at the cap (P5a). Each needs Actions write, and the lane token has none (
pr-merge.ymlnotes it). On the native path, the bounded step is the scheduled re-judge with therisk-escalation-minutesbound, then the status. A draft-toggle re-request (whole_review, pull-requests write) could serve the native path as a follow-up.P6 (the
Hold:line). report-outcome writes the line with no cause. Q-HOLDLINE is a recorded owner decision, and report-outcome belongs to the issue-lane session. Re-deriving the cause would duplicate report-outcome's trusted-labeler read.fix(source-control): bind a review re-run after a kill-switch skip #10188 S1. Checking lane-source paths at each bound SHA needs a diff read per SHA. That is a new read path.
G3 (Dependabot majors under
hold). Inert here, since this repository setsreview. Changing the code default is a policy choice.G4 (shared-copy escalation). The fix needs a push from a base merge plus
sync-shared-copies.sh, which is new write automation.Gate path (Dependabot and adopted PRs). Visibility is the existing neutral
pr-merge / gatecheck run, not a failing status. A failing gate check would be read by the gate's own checks rollup.P1b (IMPORTANT as HIGH). It conflicts with recorded owner decisions Q25, Q33 and Q693 (only CRITICAL holds), so it goes to the owner as a question. Comments now say CRITICAL, matching the code.
I2 (nothing reads
pr-merge/unarmed). That belongs to Automation Lanes' stalled-stop monitor. No timed needs-human is added.Residual:
verify:spec-changedfrom a changed closing set or a removed spec comment waits for a new head. The failing status shows it once it stands.This PR changes floor paths (
enable-auto-merge.mjs,pr-merge.yml,babysit_risk.py,pr-pipeline.yaml), so it holds for a person after it is marked ready.Related
clear-escalationsre-judges native refusals throughverifyClears, whose contract this PR keeps)🤖 Generated with Claude Code