Skip to content

feat(pr-pipeline): turn on review-lane holds and require both review lanes in ci-status - #9361

Merged
kyle-sexton merged 12 commits into
mainfrom
feat/review-lane-holds
Oct 23, 2026
Merged

kyle-sexton merged 12 commits into
mainfrom
feat/review-lane-holds

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Member

Refs: #9276

Summary

PR 6 of the #9276 plan: turn on review-lane holds. Both review lanes now add needs-human when they hold a pull request, and ci-status, the one required check, requires a counted review from each lane on the head (github-iac ADR 0017). Under OD-8 there is no lane-off job: a lane the kill switch stopped passes.

Fix

  • Callers (pr-review-hosted.yml, pr-review-security-hosted.yml): pin pr-review-cli.yml at 9aca743c4ed9aaf7870288a36586c3994e9321e4 # v0.64.1. Both set hold-label: needs-human and the same hold-paths: .github/standards/trusted-actors/**, .github/actions/check-trusted-trigger/**, **/CODEOWNERS and **/.gitmodules. Check names, docs-only-paths, cancel-in-progress: false and secrets are unchanged. v0.63.1 to v0.64.1 is the hold step (ci-workflows#864) and a fix that reds the status job on a failed publish or hold write in comments mode (ci-workflows#868).
  • ci-status step "Require both review lanes on this head" (pull_request runs on ready PRs). For each lane it reads the newest pull_request run of the caller on the head, then that run's latest jobs. The jobs are the GitHub Actions check runs, and reading them through the run keeps out runs of other workflows. The status job must have succeeded and its Apply the hold step must have run, so a summary-mode run or a run with no hold label never counts.
    • Pending, failed, cancelled, skipped or missing reviews fail, and the error names the cause.
    • A lane passes when its kill-switch job succeeded and ran Report the kill switch (OD-8). A failed kill-switch job fails.
    • Dependabot (49699333), the release App (337859524) and the standards-sync App (300666570) are exempt by author id (OD-6, extended to standards-sync by the orchestrator for the owner). Drafts are exempt.
    • Reads retry 3 times and fail closed.
  • ci-status step "Fail a review-lane hold an untrusted account lifted": a hold is the newest hold comment or the newest labeled event by github-actions[bot]/claude[bot], whichever is later. The first removal of the label after it must be by a human of the base's trusted-actor list; otherwise the step fails. The event anchor means deleting the comment does not lift the hold. A later removal belongs to a later label, such as an escalation clear-escalations lifts.
  • pr-feedback-dispatch.yml rerun-ci-status (actions: write, pull-requests: read): it runs when either review caller completes on an open same-repository PR still at that head. Skipped runs, non-PR events and forks do not start it. It re-runs only the ci-status job of the newest pr-require-checks run on the head. A ci-status that has not started yet is left alone, because it reads the reviews when it starts. One already in progress is polled every 60 s for at most 10 min. perf(pr-pipeline): skip pr-feedback-dispatch jobs that can only find nothing #9345's job gates are unchanged.
  • fix-ci dispatch keys on the ci-lanes commit status (written by github-actions[bot]), not on ci-status. Without this, every lanes App PR would summon fix-ci while its reviews ran, because the new step reds ci-status with no label on the PR. Hold-label filtering was already in place.
  • ci-status timeout-minutes goes from 5 to 7 for the new reads' retry sleeps.

Verification

  • New suites scripts/ci-review-lanes.test.sh (34), scripts/ci-hold-lifted-by.test.sh (30) and scripts/ci-rerun-ci-status.test.sh (34) run the step bodies against stub gh. scripts/ci-hold-human-gated.test.sh (29) covers the new outputs, and workflow-gates.test.mjs covers the rerun gate. All of them fail on the base workflows.
  • fix-ci-dispatch.test.mjs gains two cases that fail on the base code, a red ci-status over green ci-lanes and a ci-lanes failure or error. The fixtures in dispatch.test.mjs, callers.test.mjs and dispatch-limits.test.mjs move to ci-lanes, and 684 node tests pass.
  • scripts/check-affected.sh passes (10 passed, 3 skipped). actionlint is clean, zizmor reports no findings, the runner policy passes and lane coverage passes.
  • A security review (subagent) found one HIGH: the hold was anchored only on a deletable comment. It is fixed by the event anchor and tested.
  • Expected on this PR: ci-status fails "review pending" until both lanes finish. The listener runs from main, so it does not fire here; ci-status is re-run by hand after the reviews.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 6 commits October 23, 2026 02:50
…lanes in ci-status

Both review callers move to pr-review-cli v0.64.0 with hold-label
needs-human and the four trust-gate hold paths. ci-status requires a
counted review from each lane on the head, fails a review-lane hold
lifted by anyone but a trusted person, and pr-feedback-dispatch re-runs
ci-status when a review lane completes. fix-ci keys on ci-lanes.

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

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

chatgpt-codex-connector Bot commented Oct 23, 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-23T07:21:54.017346Z 89819d5 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89819d5fd2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/pr-require-checks.yml Outdated
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

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

Security review: PR #9361 (head 44dc7ae)

I found no security issues in this pull request.

critical-findings: 0

I read the whole diff (16 files) and the PR context. I read the workflow changes, the dispatch script change and the docs in full. I read the new and changed test suites only as far as they pin those step bodies, because they are test code.

Reviewed

  • pr-require-checks.yml, hold-lift step.
    • The hold is anchored on the newest hold comment or the newest labeled event by github-actions[bot] or claude[bot], whichever is later, so deleting the comment does not lift it.
    • The lifter must be a kind == human actor in the trusted-actor list read at BASE_SHA, which is the base commit and not the head.
    • Unreadable comments, events or lists fail closed.
    • A bot, an untrusted account, or no recorded removal all fail.
    • I traced the re-add-by-trusted-person recovery path and found no way for an untrusted account to start or end a hold with it.
  • pr-require-checks.yml, review-lane step.
    • Runs are filtered by workflow path, head SHA and event, and jobs come from that run's latest jobs, so other workflows' check runs cannot satisfy a lane.
    • A lane passes only on a successful status job whose Apply the hold step succeeded, or on a kill-switch job that succeeded and ran Report the kill switch (OD-8, the design decision that a lane the kill switch stopped passes).
    • The author exemption is by numeric PR author id. Exempt authors still fail when any changed file, or a renamed file's old path, matches a hold path.
    • Fewer listed files than changed_files fails closed.
    • $HEAD_SHA and $PR_NUMBER are regex-validated, and all values reach the shell through env:, never inline ${{ }} in run:.
    • Paths echoed into ::error:: come from @tsv, which escapes newlines, and % is escaped.
  • pr-feedback-dispatch.yml, rerun-ci-status.
    • It runs from main on workflow_run with no checkout and no model.
    • The grant is actions: write plus pull-requests: read.
    • It is gated to same-repository, pull_request-event, non-skipped runs of the two caller paths.
    • HEAD_SHA and PR_NUMBER are validated, and it acts only if the PR is open and still at that head.
    • The only write is a re-run of the head's existing ci-status job, which adds no authority beyond a manual re-run.
  • fix-ci-dispatch.mjs. It now keys on the ci-lanes commit status and accepts it only when creator.login is github-actions[bot]. A missing or foreign-written status means no dispatch, so it fails closed.
  • Callers. The pr-review-cli.yml pin is a full 40-character SHA with a version comment, and the secrets are unchanged. Pin and permission hardening is zizmor's lane and I did not re-report it. The hold-paths lists are identical in both callers and match the bash patterns used for the exempt-author check.

Manifest check

  • No .claude-plugin/plugin.json or marketplace.json changed, so there are no manifest <field> lines.

Instruction-surface check

  • The diff deletes no standing instruction, and it adds enforcement. The only removed text is the "advisory" comment wording, which is not a control.
  • I did not consult the instruction exception register, because nothing in the diff removes a rule.

Minor, not reported as findings

  • A rerun-ci-status re-run of the ci-status job reuses the original event payload, so base.sha and the trusted-actor list read from it can be one base commit stale. Moving the base would have to remove a trusted person within one run's lifetime, and a fresh push reads the current base.
  • The trust of the review-lane step rests on the pull_request workflow files being the base's, which is repository-ruleset design that this diff does not change.

No hold is needed. The diff touches none of the trust-gate paths (trusted-actors, check-trusted-trigger, CODEOWNERS, .gitmodules).

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

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

No findings on the code-review lane.

What I read. I read the PR context and diff lines 1–1072 of 1649. That covers the changelog fragment, all workflow changes (pr-require-checks.yml, pr-feedback-dispatch.yml and both review callers), fix-ci-dispatch.mjs and its tests, the pr-pipeline README section, and the start of the new shell test suites. I did not read the remaining ~575 lines, which are the rest of the new shell test suites (ci-hold-lifted-by, ci-review-lanes, ci-rerun-ci-status). They are unreviewed. I ran nothing; the pass counts in the PR description are author-claimed and unverified by me.

What I checked against the repository at the head.

  • pr-feedback-dispatch.yml:41 already lists pr-review-hosted and pr-review-security-hosted under workflow_run.workflows, so the new rerun-ci-status job can fire.
  • The kill-switch job and its Report the kill switch step exist at top level in pr-review-hosted.yml:117-156. The new ci-status step's .name == "kill-switch" and "stopped" verdict match those names.
  • The Aggregate lane results step keeps if: !cancelled() (pr-require-checks.yml:3586). A failing new review-lane or hold-lift step therefore still lets ci-lanes be written. The fix-ci switch to ci-lanes does not lose the lanes verdict while a review is pending.
  • The jq hold-lift and lane-verdict logic reads correctly for the cases I traced: paginated --jq output, slurped add[], null jobs, a skipped status job, and a stopped, pending or missing lane.
  • fix-ci-dispatch.mjs now keys on ci-lanes, with the github-actions[bot] creator check. The tests and the README text match it.

Plugin manifests. The diff changes no plugin.json or marketplace.json, so there are no manifest <field> lines.

Closing issue. The PR says Refs: #9276 and does not close it, so there are no acceptance criteria to check. The PR context has no embedded instructions aimed at the reviewer.

Hold. None. The change adds no model call, raises no limits, and touches no trusted-actors, check-trusted-trigger, CODEOWNERS or .gitmodules file. The changed files only name those paths inside hold-paths text. The security lane is out of scope here.

kyle-sexton and others added 4 commits October 23, 2026 03:26
…count lifted

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

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

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-security-review: 1 inline finding(s) on 5d1624eac3f74951e25c911bf84471ecae871cdc; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

Comment thread .github/workflows/pr-require-checks.yml
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit bfb5421 Oct 23, 2026
43 checks passed
@kyle-sexton
kyle-sexton deleted the feat/review-lane-holds branch October 23, 2026 08:18
github-merge-queue Bot pushed a commit to melodic-software/pr-pipeline-sandbox that referenced this pull request Oct 23, 2026
…#198)

Refs:
melodic-software/claude-code-plugins#9276

## Summary

PR 5 of the #9276 plan: mirrors
melodic-software/claude-code-plugins#9361 into
the sandbox so the canaries can run here.

## Fix

- Both review callers move to ci-workflows `pr-review-cli.yml` v0.64.1
(`9aca743c`), lanes `code` and `security`, with `hold-label:
needs-human` and the trust-gate `hold-paths`, the reviews role's key and
subscription secrets, and a `kill-switch` job each. No `resolve-model`
job: the reusable's default model.
- ci-status gains #9361's three steps verbatim (human-gated label, hold
lifted by an untrusted account, both review lanes on this head), a draft
note, the permissions they read with, and the `ready_for_review`,
`labeled` and `unlabeled` triggers.
- pr-feedback-dispatch.yml gains #9361's `rerun-ci-status` job;
`scripts/lanes/check-sync.sh` marks that file adapted until the next
full resync.

## Verification

actionlint passes on the four workflows. The canaries run after merge,
since the listener runs only from main.

## Related

- melodic-software/claude-code-plugins#9361

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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