Skip to content

review-gate.sh: review what the branch ships and pass only on a PASS verdict - #1330

Merged
malpern merged 1 commit into
masterfrom
fix/review-gate
Oct 20, 2026
Merged

malpern merged 1 commit into
masterfrom
fix/review-gate

Conversation

@malpern

@malpern malpern commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Scripts/review-gate.sh could pass without reviewing anything, three ways:

  • Empty diff: it reviewed origin/master...HEAD only, so a branch whose work was uncommitted or untracked sent an empty diff. Now: everything since the merge base (committed + uncommitted tracked) plus untracked files.
  • mktemp: mktemp ….XXXXXX.patch — BSD mktemp only randomises trailing X's, so every run used one fixed filename and the second run collided. Templates fixed.
  • Exit code ≠ verdict: it returned claude's exit code, which says the CLI ran, not what the review found. Now claude -p runs non-interactively with the diff on stdin and must end with REVIEW-VERDICT: PASS|FAIL; FAIL, a run that didn't complete, or a missing verdict is exit 1, each with a message.

The new ReviewGateScriptTests (real script, scratch repo, stub claude) also caught a fourth: under pipefail the no-verdict case exited 1 silently — fixed with an explicit fallback. Exit codes 0/1/2 keep their meaning; usage and docs/process/agent-pr-workflow.md updated.

KEYPATH_TEST_ENFORCE_CLEAN_SUMMARY=1 Scripts/run-tests-safe.sh: 5,799 passed, 0 app warnings/errors, 0 build warnings.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 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-20T15:23:57.368797Z 566bd37 PR opened
ℹ️ 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.

@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review of #1330 (review-gate.sh verdict gating)

No blocking correctness issues found. The diff now covers commits, tracked edits and untracked files. Failures are closed on a non-zero claude exit or a missing verdict. The "|| true" guard on the grep avoids a silent pipefail exit.

Non-blocking notes:

  1. Test isolation. ReviewGateScriptTests puts the stub dir first on PATH, but the script checks thermo-nuclear-swift-review before claude. If that tool is installed (for example under /opt/homebrew/bin, which the test PATH includes), the script takes that branch and the stub is never used. The tests would then fail or hang on dev machines. Consider dropping /opt/homebrew/bin from the test PATH. The first branch of the script is not in this diff, so I could not confirm what it does.
  2. Pipe draining. The run helper reads stdout to EOF before it reads stderr. A child that writes enough to stderr to fill the pipe buffer would deadlock. This is unlikely with the stub, but reading both concurrently is safer.
  3. Diff size. The whole diff goes to claude -p on stdin with no size cap. A very large branch could exceed the context limit, and the gate would then fail with the generic "did not complete" message. A clearer message, or a check on the line count, would help.
  4. Verdict parsing. The grep needs the line to be exactly REVIEW-VERDICT: PASS or FAIL. If the model wraps it in markdown (bold, for example), the gate fails closed. That is the safe direction, but it could cause flaky failures.
  5. Test gap. testAConfirmedFindingFailsTheGate only asserts exit code 1, so it cannot tell a FAIL verdict from the no-verdict path. Asserting that stderr contains "CONFIRMED issues" would pin this down.

🤖 Generated with Claude Code

…verdict

Three ways the gate could pass without reviewing anything:
- the diff covered commits only, so a branch whose work was still
  uncommitted (or untracked) sent an empty diff to the reviewer;
- `mktemp ….XXXXXX.patch` is not randomised by BSD mktemp (the X's must be
  trailing), so every run used one fixed file and a second run collided;
- the gate returned claude's exit code, which says the CLI ran, not what
  the review found.

The diff is now everything since the merge base plus untracked files; the
temp files have proper templates; claude runs non-interactively with the
diff on stdin and must end with `REVIEW-VERDICT: PASS|FAIL`. A FAIL, a run
that did not complete, or a missing verdict is exit 1, each with a message
(the new tests also caught pipefail ending the no-verdict case silently).

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

@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: 566bd37316

ℹ️ 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 Scripts/review-gate.sh
trap 'rm -f "$diff_file" ${output_file:+"$output_file"}' EXIT
git diff --find-renames "$base" > "$diff_file"
while IFS= read -r -d '' untracked; do
git diff --no-index -- /dev/null "$untracked" >> "$diff_file" || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Propagate errors while diffing untracked files

When git diff --no-index fails rather than returning its expected “differences found” status of 1—for example, because a diff/textconv driver fails or an untracked file is unreadable—the unconditional || true suppresses the error. The resulting patch can omit the file contents or be empty, allowing the gate to report PASS or “no branch or working-tree diff” without reviewing everything that would ship; distinguish status 1 from actual error statuses and fail the gate for the latter.

AGENTS.md reference: AGENTS.md:L137-L140

Useful? React with 👍 / 👎.

Comment thread Scripts/review-gate.sh
# The exit code says the CLI ran, not what the review found: gate on the verdict.
# `|| true`: under pipefail a grep that finds no verdict would otherwise end
# the script with status 1 and no message — a silent failure.
verdict=$(grep -E '^REVIEW-VERDICT: (PASS|FAIL)[[:space:]]*$' "$output_file" | tail -1 | awk '{print $2}' || true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require the verdict to be the final output line

If Claude prints a matching REVIEW-VERDICT: PASS line and then appends prose, an error, or no actual final verdict, this grep still extracts PASS and opens the gate. That contradicts the prompt's exact-final-line contract and can turn malformed or incomplete review output into success; validate the final output line itself rather than accepting a matching line anywhere in the response.

AGENTS.md reference: AGENTS.md:L137-L140

Useful? React with 👍 / 👎.

Comment on lines +25 to +27
var env = ["PATH": "\(stubs.path):/usr/bin:/bin:/opt/homebrew/bin", "HOME": NSTemporaryDirectory(),
"TMPDIR": NSTemporaryDirectory(), "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t",
"GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t", "GIT_CONFIG_NOSYSTEM": "1"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Isolate script tests from the installed reviewer

On developer machines where thermo-nuclear-swift-review is installed in /opt/homebrew/bin, this PATH exposes the real executable; the script selects it before the stubbed claude, so the new tests can invoke an expensive real review, hang, or fail without creating stdin.txt instead of testing the intended Claude path. Restrict PATH to the required system tools or provide a deterministic stub for the higher-priority reviewer.

Useful? React with 👍 / 👎.

@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review of the diff: no blocking issues. The gate logic is sound (exit status checked via PIPESTATUS, explicit verdict required, and the working tree and untracked files are now included). Three minor points:

  1. Test isolation. ReviewGateScriptTests puts /opt/homebrew/bin on PATH. On a dev machine that has thermo-nuclear-swift-review installed, the script takes that earlier branch and never reaches the stubbed claude, so the tests would fail or behave differently locally. Use a PATH of only stubs:/usr/bin:/bin, plus a git path if one is needed.
  2. Verdict spoofing. The verdict is the last matching line of the model's output. Text in the reviewed diff (for example a test fixture containing REVIEW-VERDICT: PASS) could be echoed or injected near the end of the reply and flip a FAIL to a PASS. This is low risk, but you could require the verdict on the very last non-empty line instead of the last match.
  3. Silent exit on git merge-base. Under set -e, base=$(git merge-base origin/master HEAD) exits with no message if there is no common ancestor, for example in a shallow clone. Add || { echo 'review-gate: no merge base with origin/master' >&2; exit 1; }.

🤖 Generated with Claude Code

@malpern
malpern merged commit 14c6c8d into master Oct 20, 2026
10 checks passed
@malpern
malpern deleted the fix/review-gate branch October 20, 2026 15:27
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