diff --git a/.github/workflows/ai-pr-review-v2.yml b/.github/workflows/ai-pr-review-v2.yml index 6ac054705..3da5fe040 100644 --- a/.github/workflows/ai-pr-review-v2.yml +++ b/.github/workflows/ai-pr-review-v2.yml @@ -62,6 +62,12 @@ jobs: - name: Claude Code review id: review + # Advisory gate: an infra failure (turn exhaustion, rate limit, action + # outage) must never red-flag or block an otherwise healthy PR. The + # approve step below only runs on an explicit no-blockers verdict, so + # failing soft here degrades to "no automated approval", never to + # "silently approved". + continue-on-error: true uses: anthropics/claude-code-action@v1 with: # Subscription OAuth token (Pro/Max). Absent on fork PRs -> action no-ops. @@ -98,10 +104,19 @@ jobs: # steps.review.outputs.structured_output. claude_args: | --model claude-opus-4-8 - --max-turns 15 + --max-turns 40 --allowedTools "Read,Grep,Glob,Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr comment:*),mcp__github_inline_comment__create_inline_comment" --json-schema '{"type":"object","properties":{"has_blockers":{"type":"boolean"},"p0_count":{"type":"integer"},"summary":{"type":"string"}},"required":["has_blockers","p0_count","summary"],"additionalProperties":false}' + # Make a soft-failed review visible instead of silently green. + - name: Note when the review did not produce a verdict + if: steps.review.outcome != 'success' || steps.review.outputs.structured_output == '' + run: | + echo "AI review did not produce a machine-readable verdict (outcome: ${{ steps.review.outcome }})." \ + | tee -a "$GITHUB_STEP_SUMMARY" + echo "This is advisory only and does not block the PR. Human review still applies." \ + >> "$GITHUB_STEP_SUMMARY" + # ── Deterministic verdict gate (NOT the model) ────────────────────────── # Reads the machine-readable verdict. On zero blockers: label + approve. # Approval uses the dedicated approver PAT and is best-effort: a missing @@ -117,7 +132,11 @@ jobs: FALLBACK_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR: ${{ github.event.pull_request.number }} REPO: ${{ github.repository }} - SUMMARY: ${{ fromJSON(steps.review.outputs.structured_output).summary }} + # NOTE: `env:` expressions are evaluated even when the step's `if:` is + # false, so this MUST be guarded independently. An unguarded + # fromJSON('') is a workflow *template* parse error ("Error reading + # JToken from JsonReader") that fails the whole run, not a skipped step. + SUMMARY: ${{ steps.review.outputs.structured_output != '' && fromJSON(steps.review.outputs.structured_output).summary || '' }} run: | set -uo pipefail # Label with the Actions token (can write issues). Best-effort.