Skip to content

feat(pr-pipeline): bind workflow-edit reviews to a run from main - #10743

Merged
kyle-sexton merged 46 commits into
mainfrom
feat/main-run-review-binding
Oct 29, 2026
Merged

kyle-sexton merged 46 commits into
mainfrom
feat/main-run-review-binding

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Member

Closes #10780

Summary

A pull request that changes a lane source (a workflow, Claude Code config, CLAUDE.md/AGENTS.md, REVIEW.md, docs/standards/**, .gitattributes, .mcp.json) could not merge without the owner. The review lanes run the head's copy of their callers, and their reviewed-head marker is posted by github-actions[bot], so a head could forge it. bind() therefore refused the review (lane-source-changed), and nothing could bind at that head. #10780 records the design. It replaces #10076's re-run fix, which cannot bind: babysit_review_evidence.py:313-318 on main unbinds from the changed paths alone. The fix here is a review whose workflow definition is always main's, bound on that run's identity and never on a comment's author.

Owner decision (Q12): option (A), approve this design. Owner, 2026-10-28 ~22:25Z, on Q12: "AGAIN GO WITH WHATEVER IS IN LINE WITH THIS:" followed by the standing direction (sections 1-8). Parent session applied it as (A). Basis: babysit_review_evidence.py:313-318 on main raises Unbound("lane-source-changed") from the changed paths alone, so a re-run review at the same head unbinds again and #10076's done-when ("fresh reviews dispatched and no needs-human") cannot be met as written; (A) keeps #10076's intent (lane-source PRs merge without the owner) under §2, keeps the boundary under §3 (review counts only from a run of main's workflow; credentialed workflows, permission widening, privileged triggers, new secrets and model legs still hold for a person), and removes a stop no re-run can clear under §4.

Rule files and .claude config (second security review, HIGH): decision basis, verbatim: owner Q11 (2026-10-28), whose every option stated "agent settings/instruction files stay gated". That is newer than and supersedes the 2026-10-22 direction the suite's test recorded for rules. Rule files are auto-loaded instructions, like CLAUDE.md.

Owner decision (Q11): owner, 2026-10-28 ~22:20Z, on Q11: "ANSWER BASED OFF OF THIS DUMMY:" followed by the standing direction (sections 1-8). The deciding lines are §4 "Aim for the highest throughput with the least friction that is safe" and §3 "Never weaken a high/critical security requirement or boundary to gain throughput". The parent session applied it as option (A): the workflow gate class keeps only the review workflows and the workflows that hold credentials beyond the job's GITHUB_TOKEN. Basis: a job token dies with its job and cannot push to main past branch protection, while an App key or PAT outlives the run.

Expected self-holds: this PR adds a model leg and secret references in a new workflow and edits gate paths. It scores agent-surface-review, secret-exposure-review and high-path:, so it needs a person by design.

Fix

  1. Main-run review (.github/workflows/pr-review-main.yml, .github/scripts/pr-review-main/).
    • Trigger and hardening. Runs on pr-require-checks completion through workflow_run. The trigger is gated by check-trusted-trigger, as in pr-verify.yml (ADR 0049): permissions: {}, a read-only review job, a main-only checkout into .base, local actions only from .base, and SHA-pinned actions.
    • Scope. Only a PR whose changed files include a lane source is reviewed, using the gate's own lane_source predicate.
    • Input. Both lanes read git diff --text of the head as a data file, so a NUL byte cannot hide a script as binary. A patch that still shows Binary files … differ or GIT binary patch, or exceeds 300000 bytes, gets no verdict.
    • Model. Each lane's skill, the review plugin's reference/, the standards and .claude/rules/ are copied from main in the plugin's layout. The model may only Read its data directory and is told never to read outside it.
    • Whole reads. One Read returns at most 25,000 tokens (tools reference), so the diff is split into parts of at most 15000 bytes and 1000 lines, and a longer line gets no verdict. write-verdict.mjs readCoverage writes a verdict only when every part was read whole, with no offset or limit.
    • Renames. The diff uses --no-renames, so a partial rename into a lane source is reviewed as the whole new file and meets the size and line caps.
    • Recorded counts. A verdict binds only when the head also carries the record job's pr-review-main: <lane> check run, posted by the github-actions app, with the same counts; a run cancelled before record binds nothing.
    • Long lines. Read may cut a long line short, so a diff line over 1800 bytes gets no verdict. The merge gate holds such a lane-source pull request for a person (long-line-review:<path>), and _main_review_counted counts no verdict whose max_line_bytes, measured by write-verdict.mjs, is past the cap.
    • Output. Each lane uploads verdict.json as pr-review-main-<lane>-<pr>-<tree>; the tree comes from git, not the model. A record job with no model and only checks: write posts each lane's counts as the check run pr-review-main: <lane> on the reviewed head. Each leg leaves a report-lane-outcome record, and the lane is listed in .github/lane-funding.json.
  2. Binding (babysit_review_evidence.py _main_bound, _main_lane, _recorded_critical; babysit_gh.collect_main_reviews, collect_main_review_critical; babysit_merge._main_reviews). For a lane-source PR, only a main-run verdict counts. Its run must be pr-review-main.yml, with event workflow_run, on the default branch, in this repository, with a head main contains, and the verdict must name the lane, PR and tree.
    • As with pr-verify's FAIL, findings count whatever the run's conclusion. Only a completed, successful run's verdict for the exact head binds.
    • Findings take the maximum over the tree's reviews. The security lane keeps any CRITICAL left open by its pull_request review.
    • It also keeps the most CRITICAL findings any pr-review-main: claude-security-review check run recorded on any head of the PR, including commits and force-pushed heads. A whitespace push to a new, clean tree therefore cannot re-roll a CRITICAL away.
    • Why check runs: the checks API has no delete, so a pull_request workflow token can delete an artifact or a comment but not a check run, and a forged one can only add a hold. A count that cannot be read leaves the lane unbound.
  3. Gate floor (babysit_risk.FLOOR_CLASSES copies regenerated by scripts/sync-hold-floor.py, under Q11 (A)). These are gate paths that need a person:
    • The review workflows.
    • Every workflow holding a credential beyond its job's GITHUB_TOKEN: a non-GITHUB_TOKEN secret, a request-lane-token mint, or id-token: write. The generator derives these rows from the workflow files (babysit_risk.gated_workflow into a GATE_WORKFLOWS block), so a workflow that gains a secret joins the gate and --check fails on a stale table.
    • .github/scripts/pr-review-main/**.
    • **/.claude/** and **/.claude-lane/** whole (settings, hooks and the scripts settings registers, skills, agents, commands, rules, output styles and memory), docs/conventions/instruction-exception-register/** and docs/conventions/untrusted-content/** (the security review's criteria), **/.mcp.json, **/CLAUDE.md, **/CLAUDE.local.md, **/AGENTS.md, REVIEW.md and docs/standards/**. These change what agents may do unattended or what later reviews judge against, and the main-run prompt rates widening them CRITICAL.
    • pr-require-checks.yml stays enforcement.
    • None of these review-surface gate rows takes feat(pr-pipeline): let a trusted author's gate-path PR clear on a passed security review #10708's trusted-author gate-path lift (babysit_risk._NO_AUTHOR_LIFT and every workflow row). Agents commit as the trusted owner, so the lift applies only to main's other gate rows (the rate guard and the remaining pr-merge scripts). docs/conventions/pr-pipeline.yaml takes feat(pr-pipeline): let a trusted author's gate-path PR clear on a passed security review #10708's stricter trust class.
    • Gated workflow count: 26 of 33 before Q11 (27 of 34 with pr-review-main.yml). Now 17 of 34.
  4. Deterministic holds extend the existing person codes in babysit_risk._cost_reasons. They run before any cost branch can skip the file, have raw-text backstops, and fall back to a text-only reading without PyYAML.
    • permission-widening-review: a job gains a write scope or write-all, or falls back to the repository default.
    • privileged-trigger-review (the text-only reader also reads the keys under a block on:, so a mapping-form issues: holds): pull_request_target, workflow_run, issues, issue_comment, pull_request_review, pull_request_review_comment, discussion, discussion_comment or repository_dispatch added.
    • secret-exposure-review: also read from head-vs-base text.
    • unreviewed-patch:<path>: a file with no patch that is not an image, font or PDF.
  5. review-path-unbindable is removed. An enforcement path beside a lane source waits for the main-run security review.

The pull_request lanes stay for least churn. They still post the inline comments people read, and ordinary PRs are unchanged.

What still needs a person, and why:

  • An edit to any of the 17 gated workflows or .github/scripts/pr-review-main/**: a review cannot be trusted to judge workflows holding its own or other credentials beyond a job token, or the code that counts its verdict.
  • An edit to anything under .claude/ or .claude-lane/, the instruction-exception register or untrusted-content conventions, .mcp.json, CLAUDE.md, CLAUDE.local.md, AGENTS.md, REVIEW.md or docs/standards/**: it changes what agents may do unattended or what reviews judge against.
  • A lane-source pull request with a diff line over 1800 bytes (long-line-review:).
  • Any workflow change that widens permissions, adds a privileged trigger, a secret reference or a model leg, and any non-media file with no patch: these are deterministic holds no AI review clears.
  • A CRITICAL finding from any main-run review of any head of the PR.
  • Everything that already held for a person (trust, cost) is unchanged.

What no longer needs a person: an edit to any of the other 17 workflows (job-token writers included, such as ci-status, intake-reenter.yml and pr-feedback-dispatch.yml), or to .gitattributes. Each still needs both main-run reviews of the exact head to be clean and no deterministic hold to fire. An enforcement floor path beside such a change also no longer needs a person.

Cost: the new review lane legs run on REVIEWS_CLAUDE_API_KEY and use the model, two legs per lane-source PR. Per the owner's standing direction §3, model usage and the promotional API credits are not cost: spending limits are set and extra usage is off, so a limit stops work and never bills.

Known follow-up: a pure rename, an empty file or a mode-only change with no patch still holds for a person as unreviewed-patch:, because GitHub's file list reports it like a changed binary file. #10873 adds the old-against-new content comparison that lets those through.

Limits: the workflow runs only once it is on main, so until this merges lane-source PRs stay unbound as today. Each lane runs one leg on the reviews role's API key. A handoff, kill switch or failure leaves no verdict, so the lane stays unbound until the next push (fail closed).

Verification

  • plugins/source-control/skills/babysit-prs/scripts: 3469 passed with PyYAML after merging main (feat(pr-pipeline): let a trusted author's gate-path PR clear on a passed security review #10708, fix(pr-pipeline): end designed stops cleanly and keep held pull requests out of the merge queue #10737), including the author-lift exclusion test and fix(pr-pipeline): end designed stops cleanly and keep held pull requests out of the merge queue #10737's carry-refuse closure. The closure required adding .github/recurring-schedule.json, plugins/evals/skills/validate/scripts/**, plugins/testing/evals/browser-probe/** and plugins/work-items/scripts/** to CARRY_REFUSES, because the newly gated workflows load them.
  • Binding tests:
    • Unbound or not binding: a forged marker on pull_request runs; another head; another tree; non-workflow_run; another branch, path or repository; an artifact of another run; an expired artifact; a head main does not hold; any malformed verdict field; a clean verdict from a failed run; unread recorded counts.
    • Binding and counting: a main-run verdict for the exact head binds both lanes. A failed run's finding still counts. Findings take the maximum over the tree. A CRITICAL an earlier head recorded holds after a clean re-roll and with no verdict at the head. A retargeted PR binds nothing.
    • End to end through the scorer: a clean main-run review makes an ordinary workflow edit mergeable, and its absence holds.
  • Collector tests: the check-run counts collector (an unreadable title counts as one CRITICAL), force-pushed heads, and the artifact collector (never downloads another run's artifact).
  • Floor tests:
    • Derived membership equals an independent reading of every workflow file.
    • Adding secrets.X, toJSON(secrets), a lane token or id-token: write gates an ungated workflow, while a comment or a quoted path does not. The generator puts a newly credentialed workflow on the floor.
    • Dropping a review workflow row is caught by --check.
    • Each agent settings or instruction file holds for a person.
  • Hold tests:
    • Each deterministic hold fires, with and without PyYAML. Each new trigger fires.
    • An issues: permission is not an issues trigger.
    • A non-media file with no patch holds as unreviewed-patch, and an image does not.
  • Mutation checks: the binder's checks and the privilege holds were each checked to fail with that check removed. The collector and floor tests were not mutation-checked.
  • Node: pr-review-main (15, adding whole-read coverage and the diff splitter; read-only review job, the record job's single scope, --text and the binary or size refusal, the lane record), pr-merge, pr-verify, pr-unblock and resolve-config (741, which caught the missing lane-funding entry behind the earlier test-node and ci-status failure) all pass.
  • actionlint, scripts/run-zizmor.sh, sync-hold-floor.py --check, check-artifact-retention.sh --check, runner-policy.mjs, ci-lanes-pr-hold.test.sh (233), prep-warnings.test.sh, ruff, typos and markdownlint pass.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 17 commits October 28, 2026 14:55
… gains for a person

Every workflow whose token can write, that names a secret other than
GITHUB_TOKEN, or that mints a lane token, and the review workflows
themselves, is now a `gate` floor path held for a person. A workflow
change that gains a write scope (or drops a permissions block onto the
repository default) holds as `permission-widening-review`, and one that
adds a pull_request_target or workflow_run trigger as
`privileged-trigger-review`; both are person codes no cost decision
clears. A secrets reference is also read from the head text, so a
workflow with no patch is not skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where PyYAML is not installed the workflow privilege holds read the text
alone: a write grant the head adds, a permissions key it drops, or an
added file with jobs and no permissions key. Tests run both readings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…binding

# Conflicts:
#	.github/workflows/pr-require-checks.yml
#	plugins/source-control/skills/babysit-prs/scripts/tests/test_babysit_risk.py
#	plugins/source-control/skills/babysit-prs/scripts/tests/test_merge_holds_sync.py
… from main

A pull request that changes a lane source (a workflow, Claude Code config,
the review criteria) now binds each review lane on the verdict artifact a
run of main's pr-review-main.yml uploaded for its exact head: the run's
path, workflow_run event, default branch, repository and completed
success, its head held by main, and the verdict's lane, pull request and
tree. Comments, markers and pull_request runs never count for it. The
most findings any main-run review of the head's tree reported count, so a
re-run never clears one. With no such verdict it stays unbound as before;
an enforcement path beside it now waits for that review instead of a
person (`review-path-unbindable` is gone).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pull request

pr-review-main.yml runs on pr-require-checks' completion through
workflow_run, so its definition is always main's. For a pull request
that changes a lane source it reviews the head's diff as data, with
each lane's criteria copied from main, on a read-only token and a
Read-only model leg, and uploads each lane's verdict as
pr-review-main-<lane>-<pr>-<tree>. It posts nothing; the merge gate
binds the lanes on that artifact through the run's record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pr-pipeline README and the babysit safety reference describe
pr-review-main.yml, how the gate binds its verdict, the workflows now on
the gate floor, and the permission-widening and privileged-trigger holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… verdicts 90 days

The verdict writer and prompt under .github/scripts/pr-review-main set
the counts the gate trusts, so they join the gate floor: a change to
them is held for a person, not cleared by the review they produce. The
verdict artifacts keep the repository maximum, so an expired verdict
with findings cannot lower the count a later clean review of the same
tree leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both lanes run as one workflow run, so a lane whose job fails concludes
the run failure after the other lane uploaded its verdict. Requiring
success before counting dropped that verdict's findings, and a clean
re-run of the same tree then bound at zero. As pr-verify does for a
FAIL, a verdict of main-run origin now counts toward the findings
whatever the run's conclusion; only a completed, successful run's
verdict naming the head binds the lane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in the ci-status suites

ci-lanes-pr-hold.test.sh now expects ci-status to hold the two workflows
and the main-run review's files as floor paths, and prep-warnings uses
an enforcement script that stays the lane's as its quiet case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 28, 2026 21:21
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

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 ✅ Completed 2026-10-28T21:24:13.692631Z c6e22c7 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.

@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 c6e22c7dda3846a770bed75203aad37dacab0de1; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

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

Incremental security review of 16b2799..6746152 for #10743. I found no security issues.

critical-findings: 0

What changed, and the control each part touches

  • Gate floor widened (tightening). pr-require-checks.yml (ci-status) moves from enforcement to gate. Five workflows that hold actions: write join the gate: intake-reenter, pr-feedback-dispatch, pr-unblock-dispatch, maintenance-reconcile-lost-runs and post-merge-demotion-alert. This changes merge-holds.mjs, babysit_risk.py, the generated hold_patterns in pr-require-checks.yml, and the hosted review caller lists. Each of these only adds paths that hold for a person, and ci-status's own file no longer takes a model verdict. The new _DISPATCH_SCOPE regex fails toward holding. It matches quoted keys, flow form and write-all, and its tests exclude actions: read and commented-out lines.
  • Generated copies agree. The hold_patterns in pr-require-checks.yml, the HOLD_PATH_CLASSES in merge-holds.mjs, the FLOOR_CLASSES in babysit_risk.py and the pr-review-*-hosted.yml path lists all drop ci-status from enforcement and add the same six workflows. I saw no case where one copy holds a path the others miss.
  • long-line-review hold narrowed (the only loosening). The hold now fires when needs_main_review is true, not for every lane-source change. A read-only workflow that readonly_workflow clears no longer triggers it, because the pull_request lanes bind such a workflow on the whole diff. This stays fail-closed, and I checked it against bind():
    • An incomplete file list (files_complete not True) gives paths=None, and needs_main_review(None, …) returns True, so the hold fires.
    • A missing or malformed base_sha fails the SHA check in readonly_workflow, so the hold fires.
    • An unreadable head file (contents_error, or a missing or malformed contents tuple) makes head_file return None, so readonly_workflow fails and the hold fires.
    • The hold clears only when every lane source passes _readonly_candidate and readonly_workflow. That excludes review callers, floor paths, deleted workflows, any write scope, secrets, and workflow_run / pull_request_target triggers. This is the same test bind() uses to choose between the pull_request lanes and the main-run path, so the hold and the binder cannot disagree.
  • base_sha threading. babysit_merge.py and babysit_verify_evidence.py now pass base_sha=json_object(pr.get("base")).get("sha") into RiskInput. A missing value gives None, which holds. It adds no new input from an untrusted source beyond the PR's own base SHA.
  • Tests, docs and changelogs. The updated floor counts (gate 48, enforcement 40, floor 106, high floor 66) match the added and removed rows. The shell test now expects pr-require-checks.yml and intake-reenter.yml to hold for a person. The README and .changes entries match the code.

Manifests: no plugin.json or marketplace.json changes in this increment.

Hold: none. Nothing here widens a trust control, a token scope, the kill switch or a merge hold. The change only adds hold paths and narrows one hold in a way that fails closed.

Spend: none changed in this increment.

@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 c6e22c7dda3846a770bed75203aad37dacab0de1; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread .github/workflows/pr-review-main.yml Outdated
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

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

Incremental review of the changes since 16b2799: no findings.

What the change does

  • pr-require-checks.yml (ci-status) moves from enforcement to gate, named by file in _NAMED_GATE_WORKFLOW, so trimming its permissions cannot drop it.
  • Every workflow whose job token has actions: write (or write-all) joins the gate through the new _DISPATCH_SCOPE regex. Six rows are added to the gate: intake-reenter, maintenance-reconcile-lost-runs, post-merge-demotion-alert, pr-feedback-dispatch, pr-require-checks and pr-unblock-dispatch.
  • _long_line_reasons now holds long-line-review: only when needs_main_review is true, and base_sha is threaded through RiskInput from both babysit_merge.score_unattended and babysit_verify_evidence.lane_risk_refusal.

What I checked

  • Counts: the changed totals are consistent with the row changes.
    • FLOOR_CLASSES goes from 101 to 106 (+6 gate rows, −1 enforcement row).
    • gate goes from 42 to 48 and enforcement from 41 to 40.
    • HIGH_FLOOR goes from 60 to 66.
    • 23 gated workflows matches the README and the changelog fragments.
  • Workflow coverage: every workflow in .github/workflows that grants actions: write is now in the gate. That covers the six added rows plus pr-merge, intake-triage and maintenance-audit-lane-runs, which were already gated.
  • Copies in sync: the generated copies agree. HOLD_PATH_CLASSES in merge-holds.mjs, FLOOR_CLASSES in babysit_risk.py, the hold_patterns array in pr-require-checks.yml, and the lists in pr-review-hosted.yml and pr-review-security-hosted.yml carry the same added and removed entries. I compared them by reading the diff. I did not run scripts/sync-hold-floor.py --check.
  • _DISPATCH_SCOPE errs toward holding:
    • It matches block, flow and quoted forms. The lookbehind excludes github-actions:, and whole-line comments are stripped before matching.
    • A stray actions: write string inside a run: body would over-gate, which fails safe.
  • _long_line_reasons: it fails toward holding.
    • If the file list is incomplete, the contents are unread, the base SHA is missing, or the path is a rename's old side, needs_main_review is true and the hold applies.
    • The head_file closure ignores its base argument. That is fine because the contents were already read at the head.
  • Test updates: the three test updates match the new classification.
    • enable-auto-merge.test.mjs moves pr-require-checks.yml into the floor list and drops the old arm expectation.
    • merge-holds.test.mjs drops it from ENFORCEMENT_FILES.
    • ci-lanes-pr-hold.test.sh expects exit 1 for pr-require-checks.yml and intake-reenter.yml instead of exit 0.
  • Not run: I could not run the test suites in this environment.

Spend: none.

kyle-sexton and others added 3 commits October 28, 2026 17:54
…nt-config gaps

- pr-review-main diffs with --text and writes no verdict for a patch
  that still holds a binary file or is over 300000 bytes. A file the
  files API sends no patch for now holds for a person as
  unreviewed-patch:<path> unless it is an image, font or PDF.
- Claude Code settings, .mcp.json, CLAUDE.md, CLAUDE.local.md,
  AGENTS.md, REVIEW.md and docs/standards/** are gate floor paths, and
  the prompt rates widening them CRITICAL.
- A record job with no model posts each verdict's counts as a
  pr-review-main: <lane> check run on the reviewed head; the gate holds
  on the most CRITICAL findings any head of the pull request recorded,
  force-pushed heads included, so a whitespace push cannot re-roll a
  CRITICAL away. Check runs cannot be deleted.
- privileged-trigger-review also covers issue, comment, review,
  discussion and repository_dispatch triggers.
- Each leg leaves a report-lane-outcome record, and the lane is in
  .github/lane-funding.json.
- The criteria keep the review plugin's layout so the skill's relative
  references resolve inside the data directory, and the prompt says not
  to read files outside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pos flag

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 d471facb22e87b01f4c8f00332fb36138f2dbc7e; 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

@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 d471facb22e87b01f4c8f00332fb36138f2dbc7e; 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_risk.py
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

The pr-verify verifier failed head 5145f57a91adb6eed0ad0d541ae5dc0f5748c316. What it wrote, as data:

Summary: Issue #10076 (refs) asks pr-merge to dispatch both review lanes once per head when evidence unbinds as `lane-source-changed`, skip the `needs-human` label in that case, and escalate on a second unbind. The owner's recorded decision fixes the design: dispatch with `workflow_dispatch` from pr-merge, keep a head-keyed marker comment, skip the label in `babysit_merge.py`, and leave the unbinding rule in `babysit_review_evidence.py` unchanged. This PR implements none of that. Its body says it "replaces the approach in #10076" with a different design: a new `workflow_run` review workflow that holds secrets, plus a rewrite of `bind()`. It also changes the unbind rule the issue requires to stay as is, names no remaining work for the issue, and adds a large amount of unrequested work on the hold floor. One piece of that work removes the `review-path-unbindable` person hold.
- plugins/source-control/skills/babysit-prs/scripts/babysit_review_evidence.py:1705: `raise Unbound("lane-source-changed")` is replaced by `_main_lane(...)` binding on a main-run verdict. This breaks the acceptance criterion "`babysit_review_evidence.py`'s unbinding rule is unchanged" and the issue's statement that the unbind rule is the security control that stays as is. Revert this change to `bind()`.
- PR body: under `Refs: #10076` it says it "replaces the approach in #10076 (re-run the lanes)". It does not name which part of #10076 it delivers or what remains for later work. A refs PR must deliver part of the issue's design, not a replacement for it. Either implement the owner-decided design or get the spec changed on the issue first.
- Spec not delivered: nothing in the diff dispatches the review lanes through `workflow_dispatch` from pr-merge, records a head-keyed marker for once-per-head, or skips the `needs-human` label in `babysit_merge.py` when every reason is `evidence-unbound` with cause `lane-source-changed`. There is also no test that a second unbind on the same head escalates, and none that other unbind causes escalate as today.
- .github/workflows/pr-review-main.yml (new): adds a privileged `workflow_run` trigger with a model leg and the `REVIEWS_CLAUDE_API_KEY` secret (`.github/lane-funding.json`). That is a new trust boundary and new spend the spec does not ask for. The decision on the issue chose `workflow_dispatch` from pr-merge instead. Remove it, or get it into the spec first.
- plugins/source-control/skills/babysit-prs/scripts/babysit_risk.py:2037-2064: removes `UNBINDABLE_PATH_PREFIX` / `review-path-unbindable`, a HIGH person hold on enforcement paths next to a lane source. That loosens a hold the owner keeps, and the spec does not ask for it. Restore it.
- plugins/source-control/skills/babysit-prs/scripts/babysit_risk.py: a large amount of out-of-scope work: 17 workflows reclassified as `gate`, new `permission-widening-review` / `privileged-trigger-review` holds, Claude settings, `.mcp.json`, CLAUDE.md, AGENTS.md, REVIEW.md and `docs/standards/**` added to the gate floor, `pr-merge.yml` and `pr-unblock.yml` moved out of `enforcement`, and regenerated copies in `merge-holds.mjs`, `pr-require-checks.yml` and `pr-review-hosted.yml`. It cites an "owner decision Q11 (A), 2026-10-28" that is not in the spec. Split it into its own issue and PR.
- PR body vs. diff mismatch: the body says `pr-require-checks.yml` moves out of `enforcement`, but the diff keeps it as `enforcement`. The body also says edits to CLAUDE.md, AGENTS.md, `.claude/**`, `docs/standards/**`, REVIEW.md and `.mcp.json` "no longer need a person", but the diff adds them as `gate` (person) paths. Make the description match the code.
- Not fully judged: I read about half of the 3,924-line diff. The rest includes the `pr-review-main.yml` workflow body, `babysit_gh.collect_main_reviews`, the `babysit_merge` changes and `scripts/sync-hold-floor.py`. These are executable, and some are in the takeover-adjacent hold floor. Split the PR: (1) the #10076 dispatch-and-label fix in pr-merge and `babysit_merge.py`, (2) the floor reclassification, (3) any main-run review workflow, each under its own spec.

This pull request leaves the merge queue, and ci-status holds it out while it carries pr-verify-failed. A pr-verify pass at a new commit removes the label.

@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 7ccef1029e0280bc795cae4aa2ea3bf32e6029ad; 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 Outdated

@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 7ccef1029e0280bc795cae4aa2ea3bf32e6029ad; 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_risk.py Outdated
kyle-sexton and others added 2 commits October 29, 2026 05:25
- Read a non-job secret as the union of the reference forms and any
  `secrets` context in a `${{ }}` span that may hold a lone `}`, so
  `format('{0}', secrets.X)`, `'}' || secrets.X`, the multi-line form and
  `if: toJSON(secrets)` gate again. The gated set stays 17 of 34.
- Hold `**/.claude/**` and `**/.claude-lane/**` whole in place of the
  per-file globs (owner decision Q11). sync-hold-floor.py refuses a glob
  whose `*` bash would let cross `/`, and a test runs the same paths
  through the Python, mjs and bash matchers.
- Count a `pr-review-main: <lane>` record only when the github-actions app
  posted it.

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 23ee4b1ba28ae8b659fa9ba3a48bade35acaa723; 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
…binding

# Conflicts:
#	plugins/source-control/skills/babysit-prs/scripts/tests/test_babysit_risk_cost.py

@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 16b27997a7eafbc4ec20c6a9b776bd8cfd5a340a; 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 Outdated

@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 16b27997a7eafbc4ec20c6a9b776bd8cfd5a340a; 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_risk.py
kyle-sexton and others added 2 commits October 29, 2026 09:46
…line holds

ci-status (pr-require-checks.yml) posts the required status and runs the
hold step, so it is now a `gate` floor path named by file in
gated_workflow, not an `enforcement` path a model verdict clears. A
workflow whose token has `actions: write` (block, flow, quoted or
write-all) can dispatch the gated credentialed workflows, so it is gated
too. sync-hold-floor.py regenerates the copies: 23 gated workflows.

long-line-review now fires only when needs_main_review, the predicate
bind() uses, says the main-run review binds the pull request. RiskInput
carries the base SHA, and the head text with_contents read stands in for
bind()'s head_file, so a read-only workflow's long line no longer holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 53459a7 Oct 29, 2026
50 checks passed
@kyle-sexton
kyle-sexton deleted the feat/main-run-review-binding branch October 29, 2026 14:17
@github-actions

Copy link
Copy Markdown
Contributor

Post-merge mutation testing

Killed 1, survived 1, timeout 0, no coverage 0, invalid 0, not run 5.
Covered-code score 50%; mutation score 50%.

File Killed Survived Timeout No coverage Invalid Not run
.github/scripts/pr-merge/merge-holds.mjs 0 0 0 0 0 1
.github/scripts/pr-review-main/write-verdict.mjs 1 1 0 0 0 0
plugins/source-control/skills/babysit-prs/scripts/babysit_gh.py 0 0 0 0 0 2
plugins/source-control/skills/babysit-prs/scripts/babysit_risk.py 0 0 0 0 0 1
scripts/sync-hold-floor.py 0 0 0 0 0 1

Surviving mutants

Location Operator Disposition Cause
.github/scripts/pr-review-main/write-verdict.mjs:94 ROR productive input-gap

The run's summary has the full report.

kyle-sexton added a commit that referenced this pull request Oct 29, 2026
…reference

The base merge at e924f7d resolved babysit_risk.py's conflict without
#10743's `${{ }}` alternative of _SECRET_REF, so `${{ secrets }}`,
`contains(secrets, 'A')` and `secrets || 'x'` inside an expression no
longer matched it. Restore the alternative and pin those shapes in a test
that fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Oct 29, 2026
…on the floor

Remove the merge.lane-arm-gate switch, the merge.person-held list, the
lane-arm scoring, the clear-escalations same-head clear and their tests:
the lane-armed set was empty, so all of it was dead code. Drop the new
floor rows the revision added, so the pull request adds no person hold.

test_merge_holds_sync.py now checks that what every trust row and the
merge-enforcement gate scripts import or run sits on a trust or gate
row. The paths it reaches today that do not are pinned in
OFF_FLOOR_ALLOWED for the owner's decision.

Refs: #10743

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Oct 29, 2026
Restores the tree of 07751e0 after owner decision Q5 turned this pull
request into the gate-attestation PR.

Refs: #10743

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Oct 29, 2026
…r-merge attestation

Owner decision Q5 option 1: babysit_risk.py and the pr-merge scripts
merge with no person once both review lanes are bound to a clean
main-run review of the head, no review thread is open, the hold floor
does not shrink against the base, and pr-merge has attested the head.

- sync-hold-floor.py generates LANE_ARMED (the merge gate's own code,
  trust rows included) and PERSON_HELD (every other trust and gate row);
  it refuses a gate row outside the merge gate's code left unlisted.
- pr-merge's lane-armed job posts pr-merge/lane-armed at the head, uploads
  the run-scoped artifact pr-merge-lane-armed-<pr>-<head>, and re-runs the
  head's red ci-status job. Each attestation and refusal is a countable
  record line.
- ci-status (merge-holds.mjs laneArmedHold) passes a lane-armed path only
  on that check run naming a pr-merge.yml run on the default branch that
  uploaded the artifact; a trusted approval still lifts it.
- pr-review-main.yml reviews a lane-armed pull request after a red
  ci-status too, which breaks the label and status cycle the same-head
  clear needed.
- Only the lanes App's own pull requests lane-arm; the 8413b31 floor
  rows are dropped.

Refs: #10743

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.

feat(pr-pipeline): bind lane-source pull request reviews to a run from main

1 participant