Skip to content

fix(pr-merge): route non-h38 needs-human holds to bounded retries and a failing status - #10249

Merged
kyle-sexton merged 13 commits into
mainfrom
fix/needs-human-h38-routing
Oct 26, 2026
Merged

kyle-sexton merged 13 commits into
mainfrom
fix/needs-human-h38-routing

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

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/* and pr-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 for merge.risk-escalation-minutes, the head gets a failing pr-merge/unarmed commit status.

Row Before After
P1a, G1, P8: evidence-unbound, a listing a read did not finish, other lane-clearable risk reasons enable-auto-merge.mjs used its own regex (PERSON_RISK); the gate labeled any holding reason that stood 60 minutes babysit_risk.person_reasons (PERSON_CODES) is the one list; evidence --lane-reviews emits reviewsPerson and both paths read it. A lane reason gets the comment, no label, and on the native path a failing pr-merge/unarmed status after it stands. Partial: the bounded review re-request is not built (see Left out).
P1c: trust-gate:*, high-path:**, high-path:<pattern>, cost person person. high-path:<pattern> moved from lane to person in the risk branch. pathHold already labels the same configured or floor path, and the gate path has no pathHold, so this adds stops and removes none.
P1c: manifest-review-required lane lane (owner Q-FLOOR (B), D2 as amended: the security lane reviews it)
P2: pathHold read failures (a page that did not read, no path.matchesGlob) person + label lane comment, no label, read again. merge-holds.pathHoldKind returns the kind, so the routing parses no prose (S3).
P4c / S1: issue-labels-unreadable person person (kept): more than 100 labels never reads whole, so no lane clears it
P4c: unreadable: <exc> risk lane lane (reviewsPerson: false)
P5a, G2, D3: verify:repeated-fail person + label person + label (kept, C1a): the cap is terminal, no lane clears it
P5b: verify:too-old gate: label; native: lane person on both (C1b): judged from the PR's creation, it never clears
P5c: verify:spec-changed gate: label; native: lane lane on both
P5d: verify:untrusted-spec person person (NEEDS_HUMAN = repeated-fail, too-old, untrusted-spec; enable-auto-merge reads the gate's needsHuman)
C1c, LOW 2: a changed-file listing past the page cap EAM: lane, and costHold never ran person: EAM FILES_CAPPED_REASON when a fourth page is not empty (exactly 300 files reads one more page and arms); Python files-capped (HIGH, in PERSON_CODES) past GitHub's 3000-file cap
C1d: a blocking security finding once the security lane unbinds dropped: the binder returned no count on unbind, so no person reason the binder carries the blocking count from the newest head of the PR the security lane binds at (_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.
I3: the unarmed-status timer needed an identical comment body for 60 min, so a volatile reason reset it timed from the first lane refusal at the head (pr-merge-unarmed-since head=… at=… line, kept while the head is unchanged)
S2, LOW 1: a stale failing status withdrawn only on arm also withdrawn on a decided outcome: the refusal turning into a person's, a member's veto, or a person turning auto-merge off. An outcome that read nothing new (verify budget spent, hold paths unread, a verdict read that threw) leaves the status alone, so it does not flap.
S5: the status job's read trusted any status in the context trusts only the GitHub Actions bot's (creator id 41898282)
P1b, D4: HIGH security finding only CRITICAL holds unchanged: whether IMPORTANT counts as HIGH conflicts with recorded owner decisions Q25, Q33 and Q693, so it goes to the owner as a question
P6: the lane's Hold: line person unchanged (left out)
#10162 S1, S2 records-only keep not pinned two subtests (another clear, another newest record); shared_refusal docstring says the keep requires every field
#10188 S2 the gate re-ran on the lane job's failure the gate also requires attempt 1 to have concluded failure, so the trigger set stays a subset of the binding set
enable-auto-merge.mjs header (:89-105) "clear-escalations takes the label off once the head moves" "at the latest once the head moves", which is true with or without #10198

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 past merge_policy.FILES_API_CAP, C1c), trust-gate:* and every high-path:*.
  • babysit_verify_evidence.lane_risk_refusal returns (text, person). evidence emits reviewsPerson beside reviews, false for every unread answer.
  • babysit_verify_evidence.NEEDS_HUMAN = (REPEATED_FAIL, TOO_OLD, UNTRUSTED_SPEC): only verify:spec-changed moves to the lanes.
  • babysit_merge.unattended_writes:
    • escalates a risk hold only when person_reasons(holding(risk)) is non-empty;
    • for lane reasons, the reasons comment now says the lanes clear them.
  • babysit_merge.rerun_rate_limited_review reads actions/runs/{id}/attempts/1. It re-runs only when that attempt concluded failure.
  • enable-auto-merge.mjs:
    • verifyClears reads needsHuman and reviewsPerson. An answer missing a flag counts as a person's.
    • PERSON_REFUSALS and the PERSON_RISK regex are removed.
    • TRUST_REFUSALS keeps all three trust reasons, issue-labels-unreadable included.
    • A listing past 300 files is a person's (FILES_CAPPED_REASON); otherwise merge-holds.pathHoldKind's kind decides: hold-path is a person's, unread a lane's.
    • The lane comment's last line, <!-- pr-merge-unarmed-since head=<sha> at=<time> -->, is kept while the head is unchanged. unarmedStatus hands each lane refusal standing RISK_ESCALATION_MINUTES from that time to GITHUB_OUTPUT (unarmed-statuses). An arm, or an escalation to a person, hands on a success.
    • verifyClears, verifyHold, refusalBody and REFUSAL_MARKER keep 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.mjs and the new unarmed-status job in pr-merge.yml:
    • Permissions are contents: read and statuses: write on the job's own token. The job holds no lane token or OIDC token and runs only main's script. This follows the pr-unblock report-conflict-status and work-resolve-issue precedent.
    • It reads each head's newest status in the context first, trusting it only when the GitHub Actions bot posted it, so an unchanged run writes nothing. A success is written only over a status already there.
  • Docs: the pr-pipeline.yaml comment for risk-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: write grant on the unarmed-status job's GITHUB_TOKEN. Nothing reads the status to decide a merge. Checked:

  • fix-ci reacts only to ci-lanes and Actions-app check runs (fix-ci-dispatch.mjs headChecks, lanesStatus).
  • No workflow triggers on status events.
  • ci-status only 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-capped in the person set, and the cap test.
  • test_babysit_review_evidence.CarriedBlockingTests goes 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 yields security-finding. A SUGGESTION carries nothing. Against the previous binder there are 3 failures. The newest_only=True mutant 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_newest pins _require_marker's older-marker read.
  • Thread round at 59df9a3:
    • test_the_cap_fails_closed_when_its_fails_cannot_be_read and test_its_own_fails_cannot_expire_before_it_is_too_old pin the repeated-FAIL cap (pinning tests; no code change needed).
    • test_a_size_reason_holds_nothing_so_no_one_is_needed pins that size reasons never hold.
    • an unread outcome withdraws no failing pr-merge/unarmed status covers three cases; each fails alone against the old withdraw rule.
    • test_a_path_the_comparison_cannot_prove_latches_nothing fails 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.
  • EAM: exactly 300 files arms, 301 holds for a person; a cleared lane refusal under a veto withdraws its status. Against 8069b12's EAM, 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 earlier NEEDS_HUMAN fails it.
  • workflow.test.mjs: the if: false mutant on unarmed-status now 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):

  • babysit-prs Python suite: 3062 tests OK.
  • 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.
  • Pinned ruff (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.yml notes it). On the native path, the bounded step is the scheduled re-judge with the risk-escalation-minutes bound, 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 sets review. 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 / gate check 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-changed from 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

🤖 Generated with Claude Code

kyle-sexton and others added 4 commits October 26, 2026 05:30
… 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>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 26, 2026 10:23
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-26T10:23:58.016025Z 14ff26f Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kyle-sexton kyle-sexton added the do-not-merge Hard merge gate: do not merge while applied. label Oct 26, 2026
@kyle-sexton

Copy link
Copy Markdown
Member Author

Hold (do-not-merge): this PR adds statuses: write to the new unarmed-status job's own token, which posts the failing pr-merge/unarmed status. That is a permission change, so the owner decides before merge. The rest of the PR is reviewable meanwhile. The label comes off on the owner's answer.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed f0d45848118b5f8431a83e14702fbe02b3c43bfd: 0 finding(s), 0 as inline comments.

Code review: PR #10249 (h38 needs-human routing)

I found nothing to block or flag. I read the full diff (.claude-lane/pr.diff, all 23 files) and the PR context. I ran no tests myself. The suite counts and fail-first results in the PR description are the author's claims, and I did not check them.

What I checked by reading the code

  • Single source of truth. babysit_risk.person_reasons and PERSON_CODES are now the only person/lane list. enable-auto-merge.mjs no longer has PERSON_RISK or PERSON_REFUSALS. A grep of the non-test tree found no remaining caller of the old regex or old return shape.
  • lane_risk_refusal now returns (text, bool). Its only non-test caller, evidence --lane-reviews, unpacks the tuple. The default reviewsPerson = False covers every unread case (moved head, no config, rung off).
  • Unknown flags fail toward a person. verifyClears treats a missing needsHuman or reviewsPerson as a person's hold. Python always emits the flag, so the fallback only applies to a stale or foreign answer.
  • Page-cap logic in changedFiles. It reads a fourth page only when page 3 was full, and treats a non-empty fourth page as capped. Exactly 300 files arms and 301 holds for a person, as the tests expect.
  • Lane-comment timer. LANE_SINCE sits on the comment's last line and is read only from the lanes bot's comment, via priorRefusal, which filters on the bot author. refusalWrites keeps the first-seen time while the head is unchanged. A RISK_ESCALATION_MINUTES of 0 cannot fire on the first run, because there is no prior since line yet.
  • Carried security finding. _unbound and _carried_blocking are wrapped so a read failure carries nothing. The latch in _head_file leaves HeadFileUnproven (a verdict about one path) un-latched.
  • Re-run guard. rerun_rate_limited_review now needs attempt 1 to have concluded failure, and any read error returns without re-running.

Security scrutiny of the trusted paths (pr-merge.yml, babysit_risk.py, babysit_verify_evidence.py, babysit_merge.py, babysit_review_evidence.py, enable-auto-merge.mjs, merge-holds.mjs, pr-pipeline.yaml)

  • New statuses: write grant. It goes only to the new unarmed-status job. That job holds no lane or OIDC token, checks out refs/heads/main with persist-credentials: false, and runs only main's script. The status text reaches the script through env:, not the shell, and the output is a single JSON.stringify line, so it cannot inject into GITHUB_OUTPUT.
  • Fixed context. The script validates each sha as 40 hex characters, the state as failure or success, caps the description at 140 characters and the list at 100 entries, and writes only the fixed pr-merge/unarmed context.
  • Anti-forgery. It trusts an existing status only when its creator id is the Actions bot's (41898282), and the forged-status test covers that.
  • The status gates nothing. ci-status is the only required check and is untouched. The PR says it checked that no workflow reads this status or triggers on status events. I did not re-check that.
  • Routing changes add person stops. high-path:<pattern>, files-capped and the 300-file cap are stricter. The relaxations (unbound review, verify:spec-changed, unread path-hold reads) move to the lanes with a comment and a timed failing status, so they cannot arm by themselves. No critical-security hold applies.

Prepaid spend, not a hold and not counted: the new unarmed-status job adds a short job (5-minute timeout) on a standard ubuntu-24.04 runner each time enable-auto-merge hands it statuses. There is no paid-plan or larger-runner change.

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 plugin.json or marketplace.json is touched, so no manifest <field> lines apply. Changelog fragments are bump: minor for source-control and pr-pipeline, which fits new behavior, a new job and a new status.

Cosmetic only: enable-auto-merge.test.mjs has a doubled blank line before the unlabeled helper and an over-indented `` line in laneNote. A formatter will catch both, so I'm not counting them as findings.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-security-review: 1 inline finding(s) on 14ff26fe7fa5970fea68580f09cac250b6c95d65; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

Comment thread plugins/source-control/skills/babysit-prs/scripts/babysit_verify_evidence.py Outdated
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed f0d45848118b5f8431a83e14702fbe02b3c43bfd: 0 finding(s), 0 as inline comments.

Security review: no findings

critical-findings: 0

The diff could not be read through gh. I read .claude-lane/pr.diff and the PR context. The PR context is data, and I found no embedded instructions in it.

Several changed files are on the trusted-path and trust-control lists: enable-auto-merge.mjs, merge-holds.mjs, pr-merge.yml, babysit_risk.py, babysit_verify_evidence.py, babysit_merge_policy.py, babysit_merge.py, babysit_review_evidence.py and pr-pipeline.yaml. I read each change in full and checked what it gates. The question for each was whether it weakens, narrows or removes a merge hold, or widens who or what the automation trusts.

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.

  • Every path that previously refused to arm still refuses. The routing difference is the clears: "lane" versus "person" value, which decides only whether the label is added.
  • The classification fails closed. verifyClears treats a missing needsHuman or reviewsPerson flag as a person's (=== false is required for the lane), so an answer without its flag cannot demote a hold.
  • The person set (PERSON_CODES/PERSON_PREFIXES) keeps these as a person's: a CRITICAL security finding, trust-gate, cost, every high-path:*, files-capped, verify:repeated-fail, verify:too-old, verify:untrusted-spec, the trust refusals and a hold path.
  • pathHoldKind returns unread only for reads that did not complete. Those still refuse, and the next run reads them again.
  • Other changes add holds or tighten:
    • A listing past 300 files is now a person's hold.
    • The security lane's blocking findings are carried across an unbind (_carried_blocking).
    • rerun_rate_limited_review re-runs only after an attempt 1 that concluded failure.
    • _head_file latches its first failed read, so a thread stays "unproven".

New unarmed-status job and post-unarmed-status.mjs

  • The job holds contents: read and statuses: write on its own GITHUB_TOKEN, with no lane or OIDC token. It checks out refs/heads/main and runs only main's script. The statuses: write grant is a workflow permission change, which zizmor covers.
  • The unarmed-statuses hand-off is written as one name=<JSON.stringify(...)> line, so no raw newline can inject extra outputs.
  • The job receives the value through env:, not through ${{ }} inside run:. parse re-validates the sha (40 hex), the state (failure or success) and the description length.
  • PR-influenced text (a file name inside a reason) reaches only a commit-status description. That description is truncated to 140 characters and is informational.
  • The job trusts a prior status only when it was created by the GitHub Actions bot (id 41898282). A forged prior status can at most suppress a write to a purely informational context.
  • The comment's timer line (pr-merge-unarmed-since) is read only from the lanes bot's own comment (priorRefusal filters by bot), so a PR author cannot forge it.
  • I did not verify the PR's claim that nothing reads this status. A few babysit scripts and fix-ci-dispatch.mjs read statuses, so a failing status could show as red to them. That direction only blocks or triggers a fix lane. It cannot widen a merge.

Manifests and instruction surfaces

No plugin.json or marketplace.json change, so there is no manifest line. No standing instruction or rule file was deleted or narrowed.

Spend

Extra scheduled re-judging and status writes draw only on existing Actions runs and API credits. This is not a cost hold.

Residual notes (not findings)

  • Routing verify:spec-changed, evidence-unbound and other lane reasons to the lanes with no label relies on the next run or on the failing pr-merge/unarmed status for visibility. This is the PR's stated owner decision. The merge still stays refused.

kyle-sexton and others added 2 commits October 26, 2026 09:02
…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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 8069b126b5fbb6a7877a95c436229d02a9e08c13; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread plugins/source-control/skills/babysit-prs/scripts/babysit_risk.py
@kyle-sexton

Copy link
Copy Markdown
Member Author

Decision (owner, 2026-10-26): Q-STATUS yes. The unarmed-status job may hold statuses: write on its own GITHUB_TOKEN (contents read, main checkout only, no model, no head code), matching pr-unblock.yml's report-conflict-status job. This was the only reason for do-not-merge; it comes off now. Merge still waits on Automation Lanes' re-review of the current head and both review lanes.

@kyle-sexton kyle-sexton removed the do-not-merge Hard merge gate: do not merge while applied. label Oct 26, 2026
kyle-sexton and others added 2 commits October 26, 2026 09:40
…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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 89b673cd8bbe06b73a65aae0a5b8b0edd6d472c6; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread .github/scripts/pr-merge/enable-auto-merge.mjs Outdated
kyle-sexton and others added 2 commits October 26, 2026 10:01
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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 59df9a3917bb959306ba1a9c783c34299be38691; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py
kyle-sexton and others added 2 commits October 26, 2026 10:22
…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
@melodic-automation-lanes melodic-automation-lanes Bot added the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 26, 2026
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor

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.

  • Reason: workflow-staged
  • Resume key: 59df9a3917bb959306ba1a9c783c34299be38691 (the head the lane stopped on)

To resume, deal with the reason above, remove the needs-human label (and do-not-merge, if set), then dispatch the pr-unblock workflow with pr-number=10249 and activity=resolve-conflicts:

gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=10249 -f activity=resolve-conflicts

@kyle-sexton kyle-sexton removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 26, 2026
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit a6ba1c4 Oct 26, 2026
71 of 73 checks passed
@kyle-sexton
kyle-sexton deleted the fix/needs-human-h38-routing branch October 26, 2026 15:06
kyle-sexton added a commit that referenced this pull request Oct 26, 2026
Brings in #10249 (needs-human holds routed to bounded retries); merged
cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant