Repository navigation
fix(pr-pipeline): hold reuse PRs on real conditions and leak-scan the reused branch - #10624
Conversation
… reused branch A reuse PR from work-resolve-issue carried "Hold: a human merges this" on every PR, and reusePlan opened it whatever the runner checks said. Both paths now ask one mustHold check in report-outcome.mjs: a trusted merge: human (now read on a reuse too, failing closed on unreadable events), untrusted-provenance, unreadable denied paths or changed-file list, a capped listing while a path is denied, a denied path, missing or failed runner checks, and on a reuse a leak scan that is missing or not clean. run-checks scans a reused branch before its checks run, with the read runner's own scan on the merge-base diff and every file it makes, and records the word in the checks record. The scan's passes move from the read runner's inline script into lib/publish-gate.mjs (scanLeak, deeperLeak, changedBytes), which the scan job already loads at the default branch tip, so both scans run one copy. The read runner's behavior tests pass unchanged. Supersedes the fixed hold from #7831. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… GAPS X5 on resume scan-reuse.mjs prints the read runner's "rotate this credential" line beside a credential-format label, and the resumed-run half of the doc bullet keeps its GAPS X5 tag, since that gap stays open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntry points 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-review reviewed I read the full diff and the touched code in The leak-scan move into One finding is inline: a reuse with zero runner checks is not held ( Not a finding, but check before merge: Spend: this change adds no paid resources. The extra Manifest items: none hit, since no critical-findings: 1 Findings not posted inline (their lines are not in the diff):
|
|
claude-security-review reviewed Security review: PR #10624The change removes the unconditional I treated this as a weakened merge hold. I found two defense-in-depth gaps in the reuse path and no CRITICAL finding. Neither is CRITICAL. Both need repository write access to a lane branch, and the denied-path hold, the runner checks and CI still run after them. This PR does not weaken critical-findings: 0 Findings
What I checked and found sound
Not counted
manifest fields: no Note: I did not read Findings not posted inline (their lines are not in the diff):
Now that a clean reuse PR carries no hold line, the only vetting of its head is the leak scan and checks at the tip If someone with push access moves the branch in that window, the PR opens with no hold. Its head is an unscanned tip, and Suggested fix: pin the plan to the checked tip. Return When
This removes the unconditional hold on a reused branch. Whether the branch qualifies is decided by Until now the fixed hold covered this gap, since a person reviewed every reuse PR. A reuse PR with a clean scan, passing checks and no denied path now opens with no hold. The lanes App's PRs are not held for human review, so any non-App commit on the branch would ride in as the App's. I could not construct a path that gets a non-App commit under an App tip. A human commit pushed on top would itself become the tip and fail Suggested fix: keep the invariant in one place by holding a reuse when |
Post-merge mutation testingKilled 2, survived 4, timeout 0, no coverage 0, invalid 0, not run 2.
Surviving mutants
The run's summary has the full report. |
No related issue: an owner-directed change to the work-resolve-issue reuse path. It supersedes the fixed reuse hold from #7831.
Summary
Until now, every reuse PR that
work-resolve-issueopened from a branch an earlier run left with commits and no report carriedHold: a human merges this, whatever the facts.reusePlanopened the PR whatever the runner checks returned, andmain()never readmerge: humanon a reuse.This PR replaces that fixed hold. The reused branch's commits get a leak scan at reuse, and the PR holds only on the real conditions, through one
mustHoldcheck thatdecide()andreusePlan()both call.Superseded: #7831 kept the automatic hold "until #7815 is proven on live runs". The leak scan now closes the gap that hold covered, and every real hold condition is kept.
Fix
report-outcome.mjsexportsmustHold. The PR holds when:merge: human, now read on a reuse too and failing closed on an unreadable event list;work-class: untrusted-provenance;clean.readChecksreads an optionalscanword:clean,foundorfailed. An unknown word reads asfailed.work-resolve-issue.yml,run-checks> "Scan the reused branch for credentials", runs only on a reuse and before any check. It calls the newscan-reuse.mjsfrom the base checkout, which scans the diff from the branch's merge base with the base tip and every file that diff makes, by name and bytes. The checks step writes the word into the record, orfailedwhen the scan covered another tip or set no word. The job keeps its existing permissions: no new token scope.leak-scanscript inpr-run-activity-read.ymlintolib/publish-gate.mjsasscanLeak,deeperLeakandchangedBytes. That covers the per-line classification, the UTF-16LE pass, the split-line pass and the scan of the files a patch makes. The scan job loads these the same way as before (API, default-branch tip, imports onlynode:builtins), so both scans run one copy and there is no second copy of the patterns. Thereviewandvisualizationcopies of the gate were regenerated withscripts/sync-shared-copies.sh.pr-run-activity.mdnow describes the reuse hold and the reuse scan. GAPS X5 stays only on the resumed-run half of that bullet, which is still open.Verification
callers.test.mjs: 195/195 pass both before and after the move, with the same pass set. EveryscanWithbehavior test passes with its text unchanged, including the warn-only label, UTF-16LE, split-line and binary-hunk tests. Two structural tests now point at the module: the minimal-gate test and the test that sliceddeeperout of the inline script.report-outcome.test.mjs: 283 pass. The 21 new tests all fail against origin/main code. They cover each hold condition, each paired with a clean reuse that does not hold;decide()/reusePlan()agreement;readChecks; main-level tests for scan, denied path, failed compare andmerge: human; the run-checks record; the reuse-scan step end to end; andscan-reuse.mjsfailure handling.lib/publish-gate.test.mjsplus everywork-resolve-issuesuite: 442 pass;explain-change.test.mjsandwrite-verdict.test.mjs: 156 pass;present.test.mjs: 52 pass;merge-holds.test.mjs: 41 pass;test_merge_holds_sync.py: OK.actionlintis clean on both workflows.sync-shared-copies.sh --checkand--check-bump origin/mainpass, as dovalidate-plugins.sh --only "review visualization"andcheck-changelog-fragments.sh.Left out:
password or secret assignmentholds a reuse PR, though the read runner only warns on it.callers.test.mjsalready trips that warning on main, so a reused branch touching it holds.Related
🤖 Generated with Claude Code