fix(claude-review): surface real review failures instead of a silent green - #117
Conversation
…green The action pin (v1.0.165→166) predates anthropics/claude-code-action#1495's fix (#1496, shipped v1.0.172): when the Claude Agent SDK call returns is_error:true, the action reported a `success` step outcome anyway, so `steps.claude-review.outcome` never reflected the real failure. Every SDK error rendered as a green check with a permanently stuck "I'll analyze this and get back to you" placeholder comment and no warning anywhere. Bump the pin to v1.0.174 (past the fix; also picks up an unrelated ghu_ token-redaction hardening from the same range) so the step outcome finally reflects is_error. Rewrite "Report review outcome" to log a real ::error:: annotation with the last SDK result (read from the action's own execution_file output — no show_full_output needed) instead of a ::warning:: that never fired, and add a sticky PR comment explicitly labeling any lingering placeholder as an incomplete infra failure, not "no findings". continue-on-error stays on the review step for now, so this doesn't newly block merges — it only makes failures visible. Verified locally: dry-ran the new shell logic against a clean-review case, an is_error execution-file case, and a missing-file case; all three branch and format correctly. actionlint clean; zizmor's one pre-existing `artipacked` finding is unchanged from main (unrelated to this change, already accepted by design per the file's own comment on the checkout step). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSCqyt1o7XnDXCgyp8ejKT
|
Claude finished @kyle-sexton's task in 2m 36s —— View job PR Review
Summary: Well-scoped, correctly reasoned fix. The diagnosis is sound, the action bump is necessary and sufficient, and the new steps are defensively coded. A few minor correctness edge cases worth flagging, but nothing blocking. SecuritySHA pinning — ✓
Permissions / security model unchanged — ✓ Correctness
In practice this only fires if a consumer incorrectly uses - if [ "$REVIEW_OUTCOME" = "success" ]; then
+ if [ "$REVIEW_OUTCOME" = "success" ] || [ "$REVIEW_OUTCOME" = "skipped" ]; then...though that arguably buries the "real" outcomes. Alternatively, handle
Code comment "never twice for the same head SHA thanks to CLAUDE.md alignmentAll load-bearing rules are preserved:
OverallThe skipped/cancelled outcome edge cases are the only actionable findings. Both are low-likelihood in normal operation (skipped requires a wrong-trigger misconfiguration; cancelled is the concurrency-group preempt path that immediately retries). The core change — bumping the action past #1496 and surfacing the failure via |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 61196e02c3
ℹ️ 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".
The is_error branch surfaced the raw last message from the action's execution_file wholesale, but per the Claude Agent SDK message schema that SDKResultMessage carries a `result` field of model-authored free text. On a public repo, dumping .[-1] into a job-log ::error:: annotation and a PR comment defeats the same log-hygiene guarantee CLAUDE.md already enforces by keeping display_report/show_full_output off. Project only the safe, structured metadata (subtype, is_error, num_turns, duration_ms, total_cost_usd) instead. Verified the jq filter against a synthetic execution_file: a normal result message keeps the metadata fields and drops result/session_id, an empty array falls through to the existing "(execution file present but unparsable)" message, and malformed JSON does too.
…-edit-last Two bugs in the infra-failure comment step: - gh pr comment needs write access, but fork-triggered pull_request runs get a read-only GITHUB_TOKEN and no secrets by GitHub's own design (the fork-review-skip contract this repo's CLAUDE.md already documents). The step ran unconditionally on review_failed, so every fork PR's SDK failure would also fail the comment call. Guard the step to same-repo PRs only (github.event.pull_request.head.repo.full_name == github.repository); a fork PR still gets the ::error:: log annotation from the prior step, just no comment it has no token to post. - --edit-last edits the PR's last comment from the authenticated user (github-actions[bot] here), not specifically the marker comment. Any newer github-actions[bot] comment from an unrelated workflow (e.g. ci-status) would get silently overwritten. Look up the existing <!-- claude-review-infra-status --> comment by ID via the REST API, scoped to github-actions[bot] authorship, and PATCH that exact comment; create a new one only when none exists. Verified the marker lookup's jq filter against a synthetic comment list containing an unrelated github-actions[bot] comment and a comment from a different user that spoofs the marker text — it correctly picks only the genuine marked comment from github-actions[bot].
|
Claude finished @kyle-sexton's task in 3m 45s —— View job Claude finished re-reviewing after the follow-up commits —— View job Re-review (commits
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6c70331de
ℹ️ 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".
… retry The infra-failure comment step only ran on review_failed == 'true', so a transient failure (e.g. an OIDC blip) that later succeeds on a rerun or new push left the "automated review did not complete" warning on the PR indefinitely — no code path ever touched the marker comment again once the review actually succeeded. Add a symmetric success-path step, gated on review_failed == 'false' and the same same-repo fork guard, that reuses the marker-comment lookup added in b6c7033 and deletes the stale comment once found. Verified: actionlint, check-jsonschema --builtin-schema vendor.github-workflows, and zizmor --offline all pass on the changed file with no new findings (the pre-existing artipacked advisory is unrelated and documented in-file).
|
Claude finished @kyle-sexton's task in 1m 38s —— View job Claude finished re-reviewing after commit Re-review (commit
|
Addresses Codex P2 finding on PR #117: the failure-only marker comment never had a success-path counterpart, so a transient infra failure (e.g. an account-side outage) that later resolves left a stale "did not complete" warning on the PR even after a clean review.
…ion' into fix/claude-review-outcome-detection
My local addition duplicated a superior version already pushed upstream on this branch (fork-guarded, marker-scoped delete vs. my plain PATCH). Keep the upstream one.
|
Claude finished @kyle-sexton's task in 3m 57s —— View job Claude finished re-reviewing after commit Re-review (commit
|
| Finding | Status |
|---|---|
| P1: Sanitize SDK result before publishing | ✅ Fixed in 05a8b3e |
| P1: Skip failure comment on fork/read-only token | ✅ Fixed in b6c7033 |
P2: Target marker comment by ID, not --edit-last |
✅ Fixed in b6c7033 |
| P2: Clear stale comment on successful retry | ✅ Fixed in 4ab4f71, duplicate resolved in 715498c |
skipped/cancelled outcome → review_failed=true |
|
--silent on PATCH/DELETE, no fallback |
skipped/cancelled edge case (previously flagged twice, still present): REVIEW_OUTCOME=skipped falls through to the failure path and posts a "infrastructure failure" PR comment. This only happens when the reject-privileged-triggers tripwire fires (i.e., a consumer already has a broken trigger), so it's noise on top of a hard failure — not a normal-operation concern. REVIEW_OUTCOME=cancelled posts a comment that gets immediately overwritten by the next push's run. Neither is blocking; both are accepted at low priority.
CLAUDE.md alignment — ✓
All load-bearing rules remain intact after this commit:
- SHA pin + version comment (
@12531344451323133b0493233c759991ac61da12 # v1.0.174): maintained ✓ pull_requestonly / tripwire: untouched ✓- Least-privilege permissions: no new grants ✓
display_report/show_full_outputoff: unchanged ✓- No untrusted checkout before action step: unchanged ✓
Overall
Commit 715498c is a clean merge-artifact cleanup with no new concerns. The PR as a whole — five substantive commits plus two merge/cleanup commits — has addressed every actionable finding from three prior review rounds. The action bump past #1496 and the failure-surfacing logic (annotation + marker comment + stale-comment clearing) are correct and minimal. Approved to merge.
## Summary Bumps the pinned `melodic-software/ci-workflows/.github/workflows/claude-review.yml` reference from `99ac2f8` to `df54d0e`. Picks up the claude-review outcome-detection fix: the reusable workflow previously reported success even when the underlying review genuinely failed. Fixed upstream in melodic-software/ci-workflows#117 (melodic-software/ci-workflows#117). One-line pin bump following the file's existing `@<sha> # <short-sha> <date>` comment convention; no other changes. ## Test plan - [ ] CI green (pr-title, markdown lint, lychee, review bot). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps the pinned `melodic-software/ci-workflows/.github/workflows/claude-review.yml` reference from `99ac2f8` to `df54d0e`. ## Why Picks up the upstream outcome-detection fix in ci-workflows: the `claude-review` reusable workflow previously reported success even when the underlying review genuinely failed. It now surfaces the real outcome. Upstream fix: melodic-software/ci-workflows#117 ## Change One-line pin bump in `.github/workflows/claude-review.yml`, following the file's existing `@<full-sha> # <short-sha> <date>` comment convention. No behavioral change in this repo beyond consuming the fixed shared workflow. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes #159. Flips the `claude-review.yml@df54d0e8…` contract from hosted-only to runner-input (`runner` + `skip-actors`, same secret and caller-permission waiver as the `4dbb0dfc` entry). `df54d0e` (melodic-software/ci-workflows#117, claude-code-action v1.0.174) surfaces real review failures — usage limits, OIDC/SDK errors, max-turns — as explicit PR comments; the six Campaign A callers pinned at `4dbb0dfc` sit in that failure-reporting regression window until they can bump to this SHA. Deviation from the issue's step 1: the issue asked to ADD a second `df54d0e` entry and keep the hosted-only one, but contract entries are keyed by `path@SHA` — one JSON key cannot carry both shapes. In-place mutation is safe instead: zero callers reference `df54d0e` on any main (verified across all nine requires-ci repos), so nothing depends on the hosted-only shape. Pin-bump wave (issue step 3) follows separately after this syncs to consumers. ## Verification - Full component suite passes (181/181). - Entry mirrors `4dbb0dfc`'s registered runner-input shape exactly, plus `runner` in `allowedInputs`. ## Related - #159 - melodic-software/ci-workflows#117 - melodic-software/github-iac#78 (epic — Campaign A follow-up) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01K3QehVwmWzkBLpKokNCkkt Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
Every
claude-reviewrun across all consumer repos has been silently reporting green for the last 27+ hours while the underlying Claude Agent SDK call fails withis_error: true(0 cost, 1 turn, <1s). No red check, no warning, just a permanently stuck "I'll analyze this and get back to you" placeholder comment on every PR.Root cause of the invisibility: our pinned
anthropics/claude-code-actionversion (v1.0.165→166) predates upstream fix #1496 for #1495 ("Action reports success when result is is_error:true"), which first shipped in v1.0.172. Before that fix,steps.claude-review.outcomewas alwayssuccessregardless of the SDK result, so our existing "Report review outcome" step's failure branch was dead code.This PR does not fix why the SDK call itself is failing — that's bisected to an Anthropic-account-side condition on the
CLAUDE_CODE_OAUTH_TOKENcredential (two independent tokens broke simultaneously with no local change; 27+ hours of continuous failure rules out a simple 5-hour rate-limit reset). That requires checking the Anthropic Console, which isn't something this PR can address. This PR makes that failure visible instead of silently green, which is the actual gap that let it run unnoticed for over a day.Changes
anthropics/claude-code-actionpin from v1.0.166 → v1.0.174 (past the #1495 fix; also picks up an unrelatedghu_token-redaction hardening from the same release range — reviewed every intermediate commit v1.0.172→v1.0.175 viagh api compare, nothing else in range).execution_fileoutput (noshow_full_outputneeded) for the last SDK result, emit a real::error::annotation (was::warning::, and never fired), and exposereview_failed/review_detailstep outputs.--edit-last --create-if-none) an explicit PR comment labeling any lingering placeholder as an incomplete infra failure — not "no findings" — with the run URL and last SDK result.continue-on-error: truestays on the review step for now — this is a visibility fix, not a new merge gate. Left as a deliberate follow-up decision whether a persistent failure should eventually block.Verification
actionlint— clean.zizmor— one pre-existingartipackedmedium finding, confirmed unchanged frommain(unrelated to this change, already accepted by design per the checkout step's own comment).is_errorwith execution file / failure with missing execution file) — all three branch and format correctly.Scope notes
runnerself-hosted-routing input (from4dbb0df) — that's a separate, deliberately staged multi-repo rollout per its own commit message.dotfiles,standards,claude-code-plugins,provisioning,github-iac,medley).🤖 Generated with Claude Code
https://claude.ai/code/session_01QSCqyt1o7XnDXCgyp8ejKT