Summary
Tonight at least seven Claude-review runs across three PRs completed green and posted nothing — no comment, no inline note, no verdict — so the review guard (#2229/#3492) failed each one as "do NOT read this PR as reviewed", and each cost a paid run (~$2.2–3.0) plus a guard cycle plus human diagnosis. On one PR the review was re-run five times on the same head with the same empty result; a fresh head then reviewed normally. The working theory on the floor was that the review is head-keyed and no-ops on a head it has already processed. That is refuted: the workflow (.github/workflows/claude-review.yml) carries no head-keyed skip, every run checks out the head and starts Claude fresh, and #3618's attempt 2 was a re-run on the same head that posted a real changes-requested. The mechanism is in the swallowed runs' own result blocks.
What the swallowed run says about itself (VERIFIED from its log)
"type": "result", "subtype": "success", "is_error": false,
"num_turns": 50, "permission_denials_count": 18, "total_cost_usd": 2.19
Fifty turns, eighteen tool-permission denials, ended "success", zero GitHub posts. The successful run on the same PR's fresh head: 65 turns, 13 denials, posted. So 50 is not a cap — Claude ended the session itself, after a run dominated by denials, without executing the verdict protocol the prompt calls mandatory.
The denials have an obvious source (VERIFIED): the prompt tells Claude "The PR branch is already checked out in the working directory" and asks for a correctness/parity/security review — an invitation to read code — while --allowedTools permits only mcp__github_inline_comment__create_inline_comment, Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr review:*). No Read, Grep, Glob, no git. Every attempt to open a file the diff touches, find a symbol's other callers, or check a parity twin is a denial. Thirteen to eighteen per run is the reviewer fighting its own harness. HYPOTHESIS (the per-turn transcript is written to claude-execution-output.json on the runner and not retained, so this cannot be proven from tonight's logs): after enough denials Claude sometimes gives up and ends with a plain-text summary — which the prompt forbids but cannot prevent — instead of gh pr review. Stochastic, so a re-run may work (as #3618's did) and may not five times running (as #3642's didn't); the empty commit "worked" as a fresh draw, not as a cache-buster.
Fix shape
- Let the reviewer read what the prompt says it can: add read-only tools to the allowlist —
Read, Grep, Glob, Bash(git diff:*), Bash(git log:*), Bash(git show:*). Fewer denials, better reviews (parity checks actually read the twin), fewer give-ups. Nothing that writes.
- The review job fails when it posted no verdict. A post-step that counts
claude[bot] reviews on the PR with submitted_at ≥ this run's start and exits non-zero with the transcript tail when the count is zero. Then a green review job means a verdict exists; a red one carries its own diagnosis; the guard becomes the second line rather than the first. (The guard stays required — it also enforces newest-verdict-wins and the drift arm.)
- Retain the transcript: upload
claude-execution-output.json as a run artifact (7-day retention) so the next swallowed run is diagnosable in one click rather than by inference.
- Operational rule (brief templates, tonight): a swallowed review is a stochastic give-up, not a stale-head no-op. Re-run once; if it repeats, the PR is likely too large for one review pass — re-run after the allowlist fix lands, or split. Do not push empty commits to "refresh" the head.
Sequencing constraint — this is why it is not laned tonight
The guard's drift arm (#2229, kept on purpose per #3492) hard-fails every PR while dev's claude-review.yml differs from main's — silent repo-wide unreview must scream. So a PR editing this workflow on dev would red the guard on every other open PR until the next dev → main sync. It has to land either (a) on main and dev together, or (b) as the last thing before a release, with the guard requirement lifted for the window per the pre-release exception. Erik's call on which; the change itself is small.
Evidence: run 35391054185 (attempt 5, head 1deff83e, swallowed) vs the fresh-head run on the same PR; #3618's same-head re-run that posted; the workflow source at dev. Cost tonight: ≥7 × ~$2.5 in review runs, ~7 guard cycles, three seats' attention. Credit: the monitoring seat for the pattern and the cost framing.
Summary
Tonight at least seven Claude-review runs across three PRs completed green and posted nothing — no comment, no inline note, no verdict — so the review guard (#2229/#3492) failed each one as "do NOT read this PR as reviewed", and each cost a paid run (~$2.2–3.0) plus a guard cycle plus human diagnosis. On one PR the review was re-run five times on the same head with the same empty result; a fresh head then reviewed normally. The working theory on the floor was that the review is head-keyed and no-ops on a head it has already processed. That is refuted: the workflow (
.github/workflows/claude-review.yml) carries no head-keyed skip, every run checks out the head and starts Claude fresh, and #3618's attempt 2 was a re-run on the same head that posted a real changes-requested. The mechanism is in the swallowed runs' own result blocks.What the swallowed run says about itself (VERIFIED from its log)
Fifty turns, eighteen tool-permission denials, ended "success", zero GitHub posts. The successful run on the same PR's fresh head: 65 turns, 13 denials, posted. So 50 is not a cap — Claude ended the session itself, after a run dominated by denials, without executing the verdict protocol the prompt calls mandatory.
The denials have an obvious source (VERIFIED): the prompt tells Claude "The PR branch is already checked out in the working directory" and asks for a correctness/parity/security review — an invitation to read code — while
--allowedToolspermits onlymcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review:*). NoRead,Grep,Glob, nogit. Every attempt to open a file the diff touches, find a symbol's other callers, or check a parity twin is a denial. Thirteen to eighteen per run is the reviewer fighting its own harness. HYPOTHESIS (the per-turn transcript is written toclaude-execution-output.jsonon the runner and not retained, so this cannot be proven from tonight's logs): after enough denials Claude sometimes gives up and ends with a plain-text summary — which the prompt forbids but cannot prevent — instead ofgh pr review. Stochastic, so a re-run may work (as #3618's did) and may not five times running (as #3642's didn't); the empty commit "worked" as a fresh draw, not as a cache-buster.Fix shape
Read,Grep,Glob,Bash(git diff:*),Bash(git log:*),Bash(git show:*). Fewer denials, better reviews (parity checks actually read the twin), fewer give-ups. Nothing that writes.claude[bot]reviews on the PR withsubmitted_at ≥ this run's startand exits non-zero with the transcript tail when the count is zero. Then a green review job means a verdict exists; a red one carries its own diagnosis; the guard becomes the second line rather than the first. (The guard stays required — it also enforces newest-verdict-wins and the drift arm.)claude-execution-output.jsonas a run artifact (7-day retention) so the next swallowed run is diagnosable in one click rather than by inference.Sequencing constraint — this is why it is not laned tonight
The guard's drift arm (#2229, kept on purpose per #3492) hard-fails every PR while
dev'sclaude-review.ymldiffers frommain's — silent repo-wide unreview must scream. So a PR editing this workflow ondevwould red the guard on every other open PR until the nextdev → mainsync. It has to land either (a) onmainanddevtogether, or (b) as the last thing before a release, with the guard requirement lifted for the window per the pre-release exception. Erik's call on which; the change itself is small.Evidence: run 35391054185 (attempt 5, head
1deff83e, swallowed) vs the fresh-head run on the same PR; #3618's same-head re-run that posted; the workflow source atdev. Cost tonight: ≥7 × ~$2.5 in review runs, ~7 guard cycles, three seats' attention. Credit: the monitoring seat for the pattern and the cost framing.