Skip to content

Claude Code Review #896

Claude Code Review

Claude Code Review #896

Workflow file for this run

name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
# NOTE: No paths-ignore here. "Claude Review" is a required status check,
# so the workflow must always run to report a result. Path filtering is
# handled inside the job via the "Check for reviewable changes" step.
pull_request_target:
types: [opened, synchronize, reopened]
issue_comment:
types: [created]
merge_group:
workflow_dispatch:
concurrency:
group: claude-review-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
jobs:
# Merge queue no-op: the real review already ran on the PR. The queue just
# needs the "Claude Review" check name to exist and pass.
merge-queue-pass:
name: Claude Review
if: github.event_name == 'merge_group'
runs-on: ubuntu-latest
steps:
- run: echo "Merge queue — skipping review (already passed on PR)"
# When the Claude review pushes an autofix commit via the GitHub App token,
# the synchronize event fires with actor=<app-name>[bot]. We don't want
# to re-run the full review (infinite loop), but "Claude Review" is a
# required check — so we need this no-op job to satisfy the gate.
# NOTE: The bot name below (rhai-org-pulse[bot]) must match the GitHub App
# configured for this repo. Update if using a different App.
# Dependabot PRs also skip here — they lack repo secrets for Vertex AI auth.
# The maintainer reviews them manually.
autofix-pass:
name: Claude Review
if: |
github.event_name != 'merge_group' &&
(github.actor == 'rhai-org-pulse[bot]' || github.actor == 'github-actions[bot]' || github.actor == 'dependabot[bot]')
runs-on: ubuntu-latest
steps:
- run: echo "Autofix push by ${{ github.actor }} — review already passed, skipping re-review"
claude-review:
name: Claude Review
timeout-minutes: 20
if: |
github.event_name != 'merge_group' &&
((github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.draft != true &&
github.actor != 'github-actions[bot]' &&
github.actor != 'rhai-org-pulse[bot]' &&
github.actor != 'dependabot[bot]') ||
(github.event_name == 'pull_request_target' &&
github.event.pull_request.head.repo.full_name != github.repository &&
github.event.pull_request.draft != true &&
github.actor != 'github-actions[bot]' &&
github.actor != 'rhai-org-pulse[bot]' &&
github.actor != 'dependabot[bot]') ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@claude') &&
github.event.comment.author_association != 'NONE' &&
github.event.comment.author_association != 'FIRST_TIMER' &&
github.event.comment.author_association != 'FIRST_TIME_CONTRIBUTOR') ||
(github.event_name == 'workflow_dispatch'))
runs-on: ubuntu-latest
steps:
- name: Determine review context
id: context
run: |
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
echo "is_fork=true" >> "$GITHUB_OUTPUT"
echo "pr_number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
else
echo "is_fork=false" >> "$GITHUB_OUTPUT"
echo "pr_number=${{ github.event.pull_request.number || github.event.issue.number }}" >> "$GITHUB_OUTPUT"
fi
- name: Check for reviewable changes
id: check-paths
run: |
PR_NUM="${{ steps.context.outputs.pr_number }}"
if [ -z "$PR_NUM" ]; then
echo "needs_review=true" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED_FILES=$(gh api repos/${{ github.repository }}/pulls/${PR_NUM}/files \
--paginate --jq '.[].filename')
needs_review=false
while IFS= read -r file; do
case "$file" in
deploy/openshift/overlays/*/kustomization.yaml)
;; # matches ignore pattern
*)
needs_review=true
break
;;
esac
done <<< "$CHANGED_FILES"
echo "needs_review=$needs_review" >> "$GITHUB_OUTPUT"
if [ "$needs_review" = "false" ]; then
echo "All changed files match ignore patterns — skipping review"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Reject fork PRs on issue_comment trigger
id: fork-guard
if: steps.check-paths.outputs.needs_review == 'true' && github.event_name == 'issue_comment'
run: |
IS_FORK=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} \
--jq '.head.repo.full_name != .base.repo.full_name')
if [ "$IS_FORK" = "true" ]; then
echo "is_fork=true" >> "$GITHUB_OUTPUT"
gh pr comment "${{ github.event.issue.number }}" \
--repo "${{ github.repository }}" \
--body "Autofix review via \`@claude\` is not available on fork PRs for security reasons. A read-only review runs automatically when the PR is opened."
echo "::warning::Skipping autofix review — fork PR detected on issue_comment trigger"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# --- Same-repo setup (full checkout, Node.js, git identity) ---
# Generate a GitHub App token so that autofix pushes trigger downstream
# workflows (CI, Integration Tests, etc.). The default GITHUB_TOKEN does
# not trigger other workflows — this is a GitHub anti-recursion safeguard.
- name: Generate GitHub App token
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork != 'true' && steps.fork-guard.outputs.is_fork != 'true'
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout code
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork != 'true' && steps.fork-guard.outputs.is_fork != 'true'
uses: actions/checkout@v7
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Set up Node.js
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork != 'true' && steps.fork-guard.outputs.is_fork != 'true'
uses: actions/setup-node@v7
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork != 'true' && steps.fork-guard.outputs.is_fork != 'true'
run: npm ci
- name: Configure git identity
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork != 'true' && steps.fork-guard.outputs.is_fork != 'true'
run: |
git config user.name "claude-code-review[bot]"
git config user.email "claude-code-review[bot]@users.noreply.github.com"
# --- Fork setup (base-branch checkout, temp branch for action) ---
- name: Checkout base branch
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork == 'true' && github.event_name == 'pull_request_target'
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.base.sha }}
- name: Setup fork PR branch for action
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork == 'true' && github.event_name == 'pull_request_target'
id: fork-setup
run: |
echo "Fork PR #${PR_NUM} detected (branch: ${HEAD_REF})"
# Check if PR modifies workflow files
CHANGED_FILES=$(gh api repos/${{ github.repository }}/pulls/${PR_NUM}/files \
--paginate --jq '.[].filename')
MODIFIES_WORKFLOWS=false
while IFS= read -r file; do
if [[ "$file" == .github/workflows/* ]]; then
MODIFIES_WORKFLOWS=true
break
fi
done <<< "$CHANGED_FILES"
if [ "$MODIFIES_WORKFLOWS" = "true" ]; then
echo "::warning::PR modifies workflow files - skipping temp branch push due to GitHub security restrictions"
echo "::notice::Review will proceed using gh pr diff only (some Read/Grep/Glob tools may not work on PR code)"
echo "modifies_workflows=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# The claude-code-action runs `git fetch origin <branchName>` internally,
# which fails for fork PRs because the branch only exists on the fork.
# Workaround: push the PR head commit to origin under a namespaced ref
# so the action's fetch succeeds. Clean up after the review completes.
SAFE_BRANCH="claude-review/fork-pr-${PR_NUM}"
git fetch origin "refs/pull/${PR_NUM}/head"
git push origin "FETCH_HEAD:refs/heads/${SAFE_BRANCH}"
echo "fork_branch=${SAFE_BRANCH}" >> "$GITHUB_OUTPUT"
echo "Pushed fork PR head to origin/${SAFE_BRANCH}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUM: ${{ github.event.pull_request.number }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
# --- Shared: GCP auth ---
- name: Authenticate to Google Cloud
if: steps.check-paths.outputs.needs_review == 'true' && steps.fork-guard.outputs.is_fork != 'true' && env.HAS_GCP_KEY == 'true'
env:
HAS_GCP_KEY: ${{ secrets.GCP_SA_KEY != '' }}
uses: google-github-actions/auth@v3
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
# --- Claude review (one step per mode, shared verdict check) ---
- name: Claude Code Review
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork != 'true' && steps.fork-guard.outputs.is_fork != 'true'
id: claude-review
uses: anthropics/claude-code-action@v1.0.216
with:
use_vertex: "true"
track_progress: true
display_report: "true"
allowed_bots: "claude"
claude_args: '--model claude-opus-4-6 --json-schema ''{"type":"object","properties":{"verdict":{"enum":["PASS","FAIL"]},"unfixed_blocking_issues":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"description":{"type":"string"}},"required":["category","description"]}}},"required":["verdict","unfixed_blocking_issues"]}'' --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr checkout:*),Bash(git add:*),Bash(git commit:*),Bash(git push origin:*),Bash(git diff:*),Bash(git status:*),Bash(npm test:*),Bash(npm run lint:*),Read,Glob,Grep,Edit,Write"'
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ steps.context.outputs.pr_number }}
You are reviewing a pull request. Your job is to:
1. **Determine PR context**:
- If no PR number is available (e.g., workflow_dispatch without context),
post a comment explaining that no PR was found and exit.
- Run `gh pr checkout <PR_NUMBER>` to switch to the PR branch.
2. **Review** the PR diff. Read `AGENTS.md` (hard constraints and
conventions) and `.github/instructions/review.instructions.md`
(review checklist and verdict rules). Apply all criteria from both.
3. **Fix** any issues you find by editing the files directly:
- Use Edit/Write tools to modify the source files
- Fix all issues you're confident about: bugs, style, security, performance
- After making all fixes, validate them:
```
npm test
npm run lint
```
- If tests or lint fail due to your changes, fix the issues or revert
your changes to that file
- Only commit and push if you actually made changes and tests pass:
```
git add -u
git diff --cached --quiet || git commit -m "fix: Claude code review autofix
<concise summary of changes>"
git push origin HEAD
```
4. **Report** your findings:
- Use inline comments for issues you found (whether or not you fixed them)
- Post a top-level PR comment summarizing:
- Issues found and fixed (with brief descriptions)
- Issues found but NOT fixed (with explanations of why)
- If no issues were found, say so briefly
5. **Set review verdict**: Follow the "Verdict rules" section in
`.github/instructions/review.instructions.md` to populate the
structured output. This is your very last action.
**Important rules:**
- Never force push. Only push to the current PR branch via `git push origin HEAD`.
- Use `git add -u` (not `git add -A`) to avoid staging untracked files.
- Be concise. Focus on actionable feedback. Don't nitpick style unless
it impacts readability.
- Only fix things you're confident about. If a fix is ambiguous or
subjective, comment instead of changing code.
- Do not modify files outside the scope of the PR unless necessary to
fix an issue (e.g., a missing import in another file).
env:
ANTHROPIC_VERTEX_PROJECT_ID: "rhai-org-pulse"
CLOUD_ML_REGION: "global"
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1"
DISABLE_PROMPT_CACHING: "1"
- name: Claude Code Review (Read-Only)
if: steps.check-paths.outputs.needs_review == 'true' && steps.context.outputs.is_fork == 'true' && github.event_name == 'pull_request_target'
id: claude-review-fork
uses: anthropics/claude-code-action@v1.0.216
with:
use_vertex: "true"
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: "*"
track_progress: true
display_report: "true"
allowed_bots: "claude"
claude_args: '--model claude-opus-4-6 --json-schema ''{"type":"object","properties":{"verdict":{"enum":["PASS","FAIL"]},"unfixed_blocking_issues":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"description":{"type":"string"}},"required":["category","description"]}}},"required":["verdict","unfixed_blocking_issues"]}'' --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Read,Glob,Grep"'
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ steps.context.outputs.pr_number }}
You are reviewing a **fork pull request**. This is a READ-ONLY review.
You must NEVER execute any code from the PR (no npm test, npm run, node,
or any other command that runs PR code). You do NOT have tools to edit
files, commit, or push — only read and comment.
1. Run `gh pr diff ${{ steps.context.outputs.pr_number }}` to get the
full diff of the PR.
2. **Review** the diff. Read `AGENTS.md` (hard constraints and
conventions) and `.github/instructions/review.instructions.md`
(review checklist and verdict rules). Apply all criteria from both.
3. **Report** your findings:
- Use inline comments for specific issues in the diff
- Post a top-level PR comment summarizing:
- Issues found (with brief descriptions and suggestions)
- If no issues were found, say so briefly
- Note that this was a read-only review (fork PR) and no autofixes
were applied
4. **Set review verdict**: Follow the "Verdict rules" section in
`.github/instructions/review.instructions.md` to populate the
structured output. This is your very last action.
**Important rules:**
- This is a FORK PR. Do NOT run any commands that execute PR code.
- Do NOT attempt to check out the PR branch.
- Use `gh pr diff` and `Read` to understand the changes.
- Be concise. Focus on actionable feedback.
env:
ANTHROPIC_VERTEX_PROJECT_ID: "rhai-org-pulse"
CLOUD_ML_REGION: "global"
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1"
DISABLE_PROMPT_CACHING: "1"
# --- Shared: verdict check and cleanup ---
- name: Check review result
if: steps.check-paths.outputs.needs_review == 'true' && steps.fork-guard.outputs.is_fork != 'true'
env:
STRUCTURED_OUTPUT: ${{ steps.claude-review.outputs.structured_output || steps.claude-review-fork.outputs.structured_output }}
run: |
if [ -z "$STRUCTURED_OUTPUT" ] || [ "$STRUCTURED_OUTPUT" = "null" ]; then
echo "::error::No structured output from Claude review"
exit 1
fi
VERDICT=$(echo "$STRUCTURED_OUTPUT" | jq -r '.verdict')
ISSUE_COUNT=$(echo "$STRUCTURED_OUTPUT" | jq '.unfixed_blocking_issues | length')
if [ "$VERDICT" = "FAIL" ] || [ "$ISSUE_COUNT" -gt 0 ]; then
echo "::error::Claude review found blocking issues that must be addressed:"
echo "$STRUCTURED_OUTPUT" | jq -r '.unfixed_blocking_issues[] | " - [\(.category)] \(.description)"'
exit 1
fi
echo "Claude review passed"
- name: Cleanup fork PR branch
if: always() && steps.fork-setup.outputs.fork_branch && steps.fork-setup.outputs.modifies_workflows != 'true'
run: |
echo "Cleaning up temporary branch: ${{ steps.fork-setup.outputs.fork_branch }}"
git push origin --delete "${{ steps.fork-setup.outputs.fork_branch }}" || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}