Repository navigation
fix(pr-pipeline): give the main-run review the lanes' credential chain and collapse exact renames - #10969
Conversation
…n and collapse exact renames pr-review-main ran on REVIEWS_CLAUDE_API_KEY alone, so an exhausted key ended every main-run review in a credit stop with no verdict and left every lane-source pull request unbound. Each lane now runs the review lanes' chain: the key leg, then review-subscription on the role's subscription token for a lane the key leg handed off (credit stop, 401, 402) or with no key, then review-fallback on the central fallback token for a lane that leg handed off. Each leg is its own job with one credential and repeats the key job's steps; route jobs with no secret pass on only lanes whose handoff reason is in a closed set. Every leg writes the same verdict, and a handed-off leg stays green. The review diff now shows a file moved unchanged as its rename lines alone (-M100%), so pure moves fit under the size cap; an edited rename, a move into or out of a lane source, and a mode change still show whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
claude-security-review reviewed Incremental review of
No spend change. No manifest fields in this diff. critical-findings: 0 |
|
claude-review reviewed Incremental review of What changed: the key leg now checks out What I checked:
No change here spends anything beyond prepaid credits or subscription usage, and nothing calls for a hold. |
|
PR body contract — issue linkage This PR body does not yet satisfy the issue-linkage contract:
Edit the body and this comment updates itself on the next run. |
…ferenced moves Security review of the main-run review diff and leg chain: - write-diff.py prints today's whole-file --no-renames patch unless it is over MAX_DIFF_BYTES. Only then do exact renames collapse, and a move whose new path (or its last directory and name) an added line names prints whole, repeated until no shown line names a collapsed move. - A move of a symlink (120000) or submodule (160000) prints whole. - The prompt says a rename-only entry's content is unseen and a change referencing a moved path is a finding. - The route jobs run on !cancelled(), so one lane's failed leg does not drop the other lane's handoff, and every leg checks out github.sha, the main commit whose copy of the workflow runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s later legs - write-diff.py prints whole, even over the size cap, a move that may run or load: an executable (100755), a path under a skills, agents, commands or hooks directory, or a file a tool reads by name (SKILL.md, hooks.json, plugin.json, marketplace.json, conftest.py, __init__.py, settings.json). A move is also shown when an added line names its bare name or its name without the extension. - review-subscription and review-fallback run on !cancelled() and their route job's success, so one lane's failed leg no longer skips the other lane's next leg through the implicit success() on every ancestor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n commit Checking every leg out at github.sha let a re-run of an old run execute that commit's review code (trusted actors, write-diff.py, the prompt, lane_source), bypassing later tightenings. The key leg checks out the main tip again and records the commit under the lane's own output; the route job passes it on only as exactly 40 hex characters and fails otherwise, and the later legs check out that commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Post-merge mutation testingKilled 6, survived 1, timeout 0, no coverage 0, invalid 0, not run 0.
Surviving mutants
The run's summary has the full report. |
Refs: #10743, #10961
Summary
pr-review-main.yml(from #10743) ran the main-bound review onREVIEWS_CLAUDE_API_KEYalone. That key's credit is exhausted. Runs 40890788166, 40891251085 and 40892239796 each endedclass=credit-stop on REVIEWS_CLAUDE_API_KEYwith no verdict. So the lanes stayed unbound, and every lane-source pull request held for a person.This PR gives the main-run review the same credential chain and handoff as the hosted review lanes. It also does part 2 of #10961: it collapses exact renames in the review diff, so a pull request of pure moves (#10952: 2.9 MB) fits under the size cap.
Fix
Credential chain. The order is taken from
pr-review-hosted.yml'ssecrets:block, and the tests assert it against that block.review(matrix of the two lanes) runs on the API key. When run-claude-cli hands off (a credit stop, or a 401 or 402), or the key is empty (no-key), the step "Hand the lane on" writes the lane's reason under the lane's own job output. Matrix outputs merge, and per-name outputs are the documented way to keep them apart. That leg skips the verdict and the outcome report and stays green.review-handoffsholds no secret, runs no model and haspermissions: {}. It checks each reason against a closed set (credit-stop auth no-key) and emits fixed lane rows as the next leg's matrix.review-subscriptionruns onREVIEWS_CLAUDE_CODE_OAUTH_TOKEN, withfallback-readyset when the fallback token is set. It hands off onlimit-stop,credit-stoporauth, or on an empty token when the fallback token is set.subscription-handoffsis a second route job with the same shape (limit-stop credit-stop auth no-key).review-fallbackruns onFALLBACK_CLAUDE_CODE_OAUTH_TOKEN. It is the last leg and hands nothing on.Each leg is its own job on a new VM with one credential. It repeats every step of the key job from a fresh main checkout: the trigger gate, the kill switch, the lane-source select, the diff, runner isolation, the read-only token, and
tools: Readwith the same allow and deny rules. Every leg writes the identicalpr-review-main-<lane>-<pr>-<tree>verdict, so therecordjob (which now needs all three legs) and the gate do not depend on which leg ran. The lane record names the credential and afallback-reasonbuilt from fixed literals, as in pr-verify. A lane that no leg gives a verdict stays unbound (fail closed).Exact renames. The new
.github/scripts/pr-review-main/write-diff.pyprints today's whole-file--no-renamespatch unless that patch is overMAX_DIFF_BYTES. Only then does it diff with-M100%, so a file moved unchanged prints only itsrename fromandrename tolines. Even then these print whole, as--no-renamesshows them:babysit_review_evidence.lane_source, the predicate the select step already uses);The prompt now says a rename-only entry's content is unseen and that a change referencing a moved path is a finding. The binary, size and long-line caps apply after.
Leg routing. The route jobs run on
!cancelled(), so one lane's failed leg does not drop the other lane's handoff. Every leg checks out${{ github.sha }}, the main commit whose copy of the workflow runs, so all legs run the same main.Registries.
.github/lane-funding.jsongains thereview-subscriptionandreview-fallbacklanes..github/artifact-retention.jsonlists the two new verdict uploaders.docs/conventions/pr-pipeline/README.mddocuments the chain and the rename rule. The change also adds a changelog fragment.Deviation from the brief. The credential check does not test every leg's secret in the key job.
callers.test.mjs:1873forbids one step from reading both an API key and an OAuth token. Instead, the key leg always hands an empty key on, and the subscription leg checks its own token and the fallback token. With nothing set, no leg reviews and nothing goes red, as before.Verification
node --testin.github/scripts/pr-review-main: 31 pass. The new tests cover:pr-review-hosted.ymland checked to be equal inpr-review-security-hosted.yml;limit-stopto the key route, using-M50%, dropping the lane-source check, dropping the under-cap whole patch, the symlink rule, the named-move rule, or its directory-and-name form each turns a test red.callers.test.mjs200 pass;check-artifact-retention.sh,runner-policy.mjs,sync-hold-floor.py --check(no hold-path change) andcheck-changelog-fragments.share clean.ci-lanes-pr-hold.test.sh243 pass,ci-review-lanes.test.sh125 pass, and the babysitreview_evidence,merge_holds_sync,risk,ghandmergesuites 619 pass.pr-review-main.yml, and main's key-only copy reviews it, so it will credit-stop with no verdict. That red run is the bug this PR fixes, not a regression. The PR holds for a person either way, becausepr-review-main.ymlis a gate floor path.Related
pr-review-cli.ymlis the hosted chain this one mirrors.🤖 Generated with Claude Code