Repository navigation
fix(ci): clear a cost-only review hold the owner already decided - #9408
Conversation
A review lane re-applies its cost hold on every new head, so needs-human came back after the owner recorded the cost decision. ci-status now clears a hold whose every lane hold since a trusted person's last removal is cost-only when the body cites the owner's unedited `**Decision recorded.** Cost:` comment, the rule babysit's cost gate reads. It comments citing the decision, removes the label, and the hold-lift step skips. trust-gate and critical-security holds are unchanged. Closes #9403 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hold comment can be edited or deleted by anyone with write access, so a trust-gate hold edited to read `criteria=cost` would have been cleared. The step now reads every hold from the `claude-lane-hold` notice each lane's status job records, keeps the label when the lanes App commented or a comment was deleted in the window, and needs `checks: read`. It also accepts a decision on an issue the body names on a `Refs:` line, which is how #9332 cites #9306. 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 security review of critical-findings: 0 What changed. The cost-hold clearing step now does three more things:
Checks I made.
The PR description already names two residual risks: No |
Most pull requests carry no lane label in the window, so the step now decides from the label events before it reads the pull request, and from the body's links before it reads closing issues, review runs, annotations or GraphQL. The verdict is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
claude-review reviewed Incremental review of c77199e..2587923 ( What changed:
Checked and sound:
No hold: the change adds no model or API spend, touches no trust-gate paths, and raises no critical security finding. |
The PR author wrote both the issue and the link the step accepted, so an owner cost decision on any unrelated issue named on a Refs: line could clear any PR's cost hold. The step now accepts, like babysit's cost_decision, only a decision on the pull request itself or on the one issue GitHub records it as closing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pull_request run takes its workflow from the pull request, so a head that edits a review-lane workflow could write a cost-only hold notice over a trust-gate hold. The step now compares each lane run's head with the base and keeps the hold when it changes either lane workflow, or the comparison lists 300 files (the API's cap) or does not parse. The jobs and annotations reads now take every page, so a hold notice past the first 100 is seen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
The pr-verify verifier failed head 258792331a8d5fd1991eb08e9493acf214ad4fe3. What it wrote, as data:
Summary: #9403 asks that a review lane's `cost` hold not stick when the PR's closing or linked issue has an owner-authored `**Decision recorded.** Cost:` comment newer than the PR's createdAt. The new ci-status step only clears the hold when the PR body links that comment, and it ignores linked (`Refs:`) issues. A decision recorded on the closing issue, which is the #9224 rule the issue cites, leaves the PR held unless someone also edits the body to cite it. The PR body itself says the motivating case, #9332, is still not fixed. The rest of the step is careful and well tested: it fails closed, keeps trust-gate and critical-security holds, reads holds from notices rather than comments, and vets lane workflows. But the done-when condition is only met in a narrower case than the issue asks for.
- The review comments on the PR are the owner's (author_id 153232337) and explain design choices. They are context, not spec. I saw no text trying to steer the reviewer.
- .github/workflows/pr-require-checks.yml:124: the step finds a decision only through a `#issuecomment-` link in the PR body (`[ -n "$links" ] || keep "the body links no comment"`). The spec counts a decision when the closing issue carries the comment, with no body citation. A decision must also be newer than the PR's createdAt, so a decision on the closing issue never clears the hold unless the body is edited afterwards. Fix: read the comments of the one closing issue (and of the PR) directly, and apply the same author, unedited, first-line and newer-than-createdAt checks, instead of requiring a body link.
- .github/workflows/pr-require-checks.yml:133: only GitHub's `closingIssuesReferences` (totalCount == 1) counts, and a `Refs:`-linked issue never does. The spec says "closing or linked issue", and its motivating case #9332 links #9306 only through `Refs:`. The PR body admits #9332 stays held. Either support a linked issue in a way that resists the unrelated-issue exploit (for example, require the issue to be referenced in the PR body outside HTML comments and the decision to be newer than createdAt, as the spec states), or get #9403's done-when amended to closing-issue-only before merging this narrowing.
- .github/workflows/pr-require-checks.yml:146: owner identity is any `human` on the base trusted-actor list, not the single owner account the spec names. The PR says this matches babysit's cost_decision rule, which the spec accepts. I could not verify that against babysit_merge_policy.py.
- .github/workflows/pr-require-checks.yml:3117: ci-status gains `checks: read` and its timeout goes from 7 to 9 minutes. Both serve the new step and look proportionate.
- .github/workflows/pr-require-checks.yml:236: the hold-lift step now skips when cleared=true. That also skips it when an untrusted account removed a cost-only hold that the owner's decision covers. This seems to be intended, but a human should confirm it.
There was a problem hiding this comment.
The pr-verify verifier failed head 258792331a8d5fd1991eb08e9493acf214ad4fe3. What it wrote about this file, as data:
- scripts/ci-hold-cost-decision.test.sh:437: the positive case only covers a body that cites the decision link. Add a case where the closing issue carries the owner's decision and the body has no link (expected: cleared), plus whatever linked-issue case the spec ends up requiring.
Closes #9403
Summary
A review lane re-applies its
costhold on every new head, soneeds-humancame back after the owner had already recorded the cost decision (#9332). ci-status now clears such a hold itself: the owner's recorded decision is enough for a cost-only hold, and trust-gate and critical-security holds still need a person.Fix
Clear a cost-only review hold the owner decided(idcost_hold), runs before the human-gated step. It clears the hold only when all of these are true in the window that opens at a trusted person's last removal of the label:costalone. The step reads each hold from theclaude-lane-hold ... criteria=notice on the lane's status-job check run (every attempt), not from the hold comment, which anyone with write access can edit or delete. Jobs and annotations are read across every page. Apull_requestrun takes its workflow from the PR, so a lane run whose head changespr-review-hosted.ymlorpr-review-security-hosted.ymlfrom the base keeps the hold, as does a comparison that lists 300 files (the API's cap) or does not parse;cost_decisionauthor rule: a trusted-actorhuman, not posted through an App, never edited, first line**Decision recorded.** Cost:. The decision sits on the PR itself, or (written after the PR) on the one issue GitHub'sclosingIssuesReferencesrecords the PR as closing, as babysit'scost_decisionreads it. ARefs:line or a second closing issue counts for nothing, so an owner decision on an unrelated issue cannot clear the hold.cleared=true. An unreadable input clears nothing, so the existing steps judge the label as before.checks: readfor the annotations, and its timeout goes from 7 to 9 minutes.Verification
scripts/ci-hold-cost-decision.test.sh(new): 69 pass, including that a PR with no lane label event stops after the label-event read and one with no link in its body stops before any run or GraphQL read. Each case says whether the hold clears and, for a kept hold, why. A hold comment edited to readcostover a trust-gate notice is kept: the first version of this step cleared it, and this test fails against that version. A decision on an unrelated issue the body names on aRefs:line, or on one of two closing issues, clears nothing: both cases fail against eb5c79e. A lane run on a head that changes a lane workflow, a capped or unparseable comparison, and unpaginated jobs or annotations reads each keep the hold, and all four fail against c77199e.scripts/ci-hold-lifted-by.test.sh(33 pass) andscripts/ci-hold-human-gated.test.sh(29 pass).Refs:and closes no issue, so this step no longer clears its hold; a person lifts it.scripts/check-affected.sh --testsin thescripts/setup-env.shvenv: 10 passed, 4 skipped. actionlint is clean, and zizmor reports no findings.review:security-reviewerpass on the first version found that hold comments can be edited or deleted. Both are fixed here.Related
cost_decision(plugins/source-control/skills/babysit-prs/scripts/babysit_merge_policy.py) accepts: the PR itself or its one closing issue.Closes #Mmakes a decision on #M count. Merging then closes #M, which is visible.pr-require-checks.ymlcan change this step. That trust posture is unchanged.🤖 Generated with Claude Code