You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On PR #4059 (head c94be900), both AI review lanes concluded success and produced no review and no finding. The check rows went green, the required ci-status check went green, and the PR was mergeable on that evidence. The only substantive review on that PR came from Codex, which found three real bugs.
The initial hypothesis was quota exhaustion on the account behind the lanes. That is not what happened. Both sessions ran, were billed, and exited cleanly well inside every budget.
Evidence
From the two sessions' own result records in the job logs:
No quota, rate-limit, 429, overload, or credit signal appears anywhere in either log. Neither ran out of turns or time. Roughly $3.81 was spent to produce nothing, and both lanes reported green.
Each session's tracking comment records its own incomplete state. claude-review logged:
Invoke /review:code-review (skill errored in this environment; proceeding with an equivalent manual review: correctness + maintainability, security out of scope)
and then stopped with all seven of its review items unchecked. claude-security-review stopped with all four of its passes unchecked.
Root cause, and what is inferred rather than proven
Proven: the string Skill appears zero times in the claude-review job log, including in the resolved ALLOWED_TOOLS, which is:
plus Bash(gh pr diff:*) and mcp__github_inline_comment__create_inline_comment from claude_args. The prompt handed to that session is Invoke /review:code-review now and follow its instructions exactly for this pull request. The tool required to satisfy that instruction is not in the grant.
Inferred: that the missing Skill grant accounts for the 23 denials. The per-turn stream is not in the job log (only the init and result records are), so the denied tool names cannot be read back. The count, the absent grant, and the session's own admission that the skill errored all point the same way, but the attribution is not directly observable from the log.
This is the same failure named in #3147 ("claude-review lane cannot invoke /review:code-review: Skill tool errors in the workflow environment, lane degrades to manual review"), which is closed with all three of its acceptance criteria unchecked. Criteria 2 and 3 are both still failing:
"A claude-review run on a PR invokes /review:code-review successfully, or the lane fails loudly when it cannot"
"The silent-degrade path (manual review with a green check row) no longer masks a broken skill chain"
The guard built for this does not catch it
review-skill-evidence exists in .github/workflows/claude-review.yml precisely to redden this class, and it passed. Reproduced locally against the real PR at the real head:
$ GITHUB_EVENT_NAME=pull_request GITHUB_ACTOR=kyle-sexton PR_NUMBER=4059 \
EVENT_HEAD_SHA=c94be900... LANE_RESULT=success REVIEWER_LOGINS='claude[bot]' \
bash scripts/verify-claude-review-skill.sh
claude-review skill evidence OK (no silent Skill-tool fallback in posted review bodies)
$ echo $?
0
The guard fetches review bodies posted by claude[bot] and classifies each one for a Skill-failure admission. On this PR claude[bot] posted 0 reviews and 3 issue comments. With no review body to classify, the loop body never executes and the script falls through to its success line.
Two gaps compound:
Absence of a bad body is read as absence of a problem. The guard can only find a degrade inside a posted review. A lane that posts nothing has nothing to classify and passes. "No review was posted" and "a clean review was posted" are the same verdict.
The sibling verify-security-review-evidence.sh is structurally sounder: it branches on LANE_REVIEW_RAN, a positive declaration that a review ran, and errors when that is false. It passed here because the upstream reusable declared review_ran=true, which evidently means "the session ran", not "a review was produced".
Grant the Skill tool to the review lane's session, or confirm with the reusable workflow's owner how a skill is meant to be invoked there, and make the lane fail loudly when the invocation is refused.
Give verify-claude-review-skill.sh a zero-review branch: an in-scope lane that concluded success and posted no review body at all is a failure, not a pass. Distinguish "reviewed and found nothing" from "never reviewed".
Have the guard read the tracking issue comment as well as review bodies, since that is where a degrading session actually confesses.
Reconcile what review_ran=true means upstream. If it reports session completion rather than review production, the security guard's central signal does not mean what its name says.
Consider surfacing permission_denials_count from the result record as a lane signal. A double-digit denial count is a strong indicator that a session could not do the job it was given.
Acceptance criteria
A claude-review run that cannot invoke /review:code-review fails its check row rather than reporting success.
verify-claude-review-skill.sh fails for an in-scope lane that concluded success and posted no review body, with a regression case covering the zero-review input.
verify-security-review-evidence.sh's LANE_REVIEW_RAN signal is confirmed to mean a review was produced, or the guard stops relying on it as though it does.
A lane that produced no review and no finding is visible as such on the PR, without reading the job log.
Workaround now in place
plugins/source-control 0.55.75 adds step B2 to the PR monitor's per-iteration checklist: a review lane that posted no review body and no finding is classified ABSENT rather than PASS, and a local review (/review:fanout, or /review:code-review for the correctness lane alone) is run over the same diff and named in the report. The readiness gate in step E does not clear until every review lane is productive or substituted. That is an agent-side compensation for a CI-side defect and does not close this issue.
This was generated by AI during work.
What happened
On PR #4059 (head
c94be900), both AI review lanes concluded success and produced no review and no finding. The check rows went green, the requiredci-statuscheck went green, and the PR was mergeable on that evidence. The only substantive review on that PR came from Codex, which found three real bugs.The initial hypothesis was quota exhaustion on the account behind the lanes. That is not what happened. Both sessions ran, were billed, and exited cleanly well inside every budget.
Evidence
From the two sessions' own result records in the job logs:
claude-reviewclaude-security-reviewsubtype/is_errorsuccess/falsesuccess/falsepermission_denials_counttotal_cost_usdNo quota, rate-limit, 429, overload, or credit signal appears anywhere in either log. Neither ran out of turns or time. Roughly $3.81 was spent to produce nothing, and both lanes reported green.
Each session's tracking comment records its own incomplete state.
claude-reviewlogged:and then stopped with all seven of its review items unchecked.
claude-security-reviewstopped with all four of its passes unchecked.Root cause, and what is inferred rather than proven
Proven: the string
Skillappears zero times in theclaude-reviewjob log, including in the resolvedALLOWED_TOOLS, which is:plus
Bash(gh pr diff:*)andmcp__github_inline_comment__create_inline_commentfromclaude_args. The prompt handed to that session isInvoke /review:code-review now and follow its instructions exactly for this pull request.The tool required to satisfy that instruction is not in the grant.Inferred: that the missing
Skillgrant accounts for the 23 denials. The per-turn stream is not in the job log (only theinitandresultrecords are), so the denied tool names cannot be read back. The count, the absent grant, and the session's own admission that the skill errored all point the same way, but the attribution is not directly observable from the log.This is the same failure named in #3147 ("claude-review lane cannot invoke
/review:code-review: Skill tool errors in the workflow environment, lane degrades to manual review"), which is closed with all three of its acceptance criteria unchecked. Criteria 2 and 3 are both still failing:/review:code-reviewsuccessfully, or the lane fails loudly when it cannot"The guard built for this does not catch it
review-skill-evidenceexists in.github/workflows/claude-review.ymlprecisely to redden this class, and it passed. Reproduced locally against the real PR at the real head:The guard fetches review bodies posted by
claude[bot]and classifies each one for a Skill-failure admission. On this PRclaude[bot]posted 0 reviews and 3 issue comments. With no review body to classify, the loop body never executes and the script falls through to its success line.Two gaps compound:
skill errored in this environmentsentence was in the tracking issue comment, not in a review body. The guard reads review bodies by design (deliberately not the job log, per security-evidence guard fails every successful in-scope PR by matching the lane's echoed script source #2517), so the one piece of direct evidence sat where it could not be seen.The sibling
verify-security-review-evidence.shis structurally sounder: it branches onLANE_REVIEW_RAN, a positive declaration that a review ran, and errors when that isfalse. It passed here because the upstream reusable declaredreview_ran=true, which evidently means "the session ran", not "a review was produced".Proposed work
Skilltool to the review lane's session, or confirm with the reusable workflow's owner how a skill is meant to be invoked there, and make the lane fail loudly when the invocation is refused.verify-claude-review-skill.sha zero-review branch: an in-scope lane that concluded success and posted no review body at all is a failure, not a pass. Distinguish "reviewed and found nothing" from "never reviewed".review_ran=truemeans upstream. If it reports session completion rather than review production, the security guard's central signal does not mean what its name says.permission_denials_countfrom the result record as a lane signal. A double-digit denial count is a strong indicator that a session could not do the job it was given.Acceptance criteria
claude-reviewrun that cannot invoke/review:code-reviewfails its check row rather than reporting success.verify-claude-review-skill.shfails for an in-scope lane that concluded success and posted no review body, with a regression case covering the zero-review input.verify-security-review-evidence.sh'sLANE_REVIEW_RANsignal is confirmed to mean a review was produced, or the guard stops relying on it as though it does.Workaround now in place
plugins/source-control0.55.75 adds step B2 to the PR monitor's per-iteration checklist: a review lane that posted no review body and no finding is classified ABSENT rather than PASS, and a local review (/review:fanout, or/review:code-reviewfor the correctness lane alone) is run over the same diff and named in the report. The readiness gate in step E does not clear until every review lane is productive or substituted. That is an agent-side compensation for a CI-side defect and does not close this issue.Related