Skip to content

Function name changes #133

Function name changes

Function name changes #133

name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
# Serialize runs per PR. The stash/restore reviewer dance below assumes
# only one run is mutating the PR's reviewer list at a time; without
# this, an overlapping second run reads an already-cleared list, stashes
# [], and on restore wipes the original reviewers permanently. Cancel
# the in-flight run on a new push so the freshest diff wins.
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
# The reviewer stash/restore is a serialization signal: we clear the
# human reviewers before Claude runs so they aren't notified mid-run,
# then re-add them when Claude finishes so the re-add fires a fresh
# GitHub notification — letting the human see Claude's review before
# they start their own. We also snapshot the PR head SHA so the
# post-Claude step can detect whether Claude pushed any commits and
# route the re-request accordingly (human assignees vs. original
# reviewer set). The restore is load-bearing for the notification
# signal, so it must NOT silently swallow errors (see restore step
# below).
- name: Stash and clear reviewers; record starting head SHA
id: stash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
HEAD_SHA=$(gh api "repos/$REPO/pulls/$PR_NUMBER" --jq '.head.sha')
echo "head_before=$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "Starting PR head: $HEAD_SHA"
REVIEWERS_JSON=$(gh api "repos/$REPO/pulls/$PR_NUMBER/requested_reviewers")
USERS=$(echo "$REVIEWERS_JSON" | jq -c '[.users[].login]')
TEAMS=$(echo "$REVIEWERS_JSON" | jq -c '[.teams[].slug]')
echo "users=$USERS" >> "$GITHUB_OUTPUT"
echo "teams=$TEAMS" >> "$GITHUB_OUTPUT"
echo "Stashed users: $USERS"
echo "Stashed teams: $TEAMS"
if [ "$USERS" != "[]" ] || [ "$TEAMS" != "[]" ]; then
# `|| true` is intentional and asymmetric with the restore step:
# a failed stash leaves reviewers in place, which only causes a
# mid-run notification (recoverable). A failed restore would
# silently drop them entirely (not recoverable) — see that step.
jq -n --argjson users "$USERS" --argjson teams "$TEAMS" \
'{reviewers: $users, team_reviewers: $teams}' \
| gh api -X DELETE \
"repos/$REPO/pulls/$PR_NUMBER/requested_reviewers" \
--input - || true
fi
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# Force tag mode (anthropics/claude-code-action src/modes/detector.ts).
# In the default agent mode for pull_request events, the action never
# posts any non-inline PR comment by itself (src/modes/agent/index.ts:
# "No tracking comment in agent mode", commentId: undefined). The
# code-review plugin will only post a top-level comment if it finds
# issues scoring >= 80, so silent runs are common on small/mechanical
# PRs. track_progress forces tag mode, which creates a tracking
# comment up front via createInitialComment() and updates it at the
# end — guaranteeing a PR comment regardless of plugin output.
track_progress: 'true'
# Bundle the tracking comment into a single sticky per PR rather than
# a new comment each run. (Only consulted in tag mode.) Each new run
# updates the same comment in place — the latest review is always
# visible, and prior reviews from `@claude review` invocations (which
# post as separate non-sticky comments) are left untouched.
use_sticky_comment: 'true'
# Also archive the formatted report to the Actions step summary page
# (does not affect PR comments).
display_report: 'true'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
- name: Re-assign reviewers after Claude finishes
if: always() && steps.stash.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_BEFORE: ${{ steps.stash.outputs.head_before }}
USERS_BEFORE: ${{ steps.stash.outputs.users }}
TEAMS_BEFORE: ${{ steps.stash.outputs.teams }}
run: |
PR_JSON=$(gh api "repos/$REPO/pulls/$PR_NUMBER")
HEAD_AFTER=$(echo "$PR_JSON" | jq -r '.head.sha')
echo "Head before Claude: $HEAD_BEFORE"
echo "Head after Claude: $HEAD_AFTER"
if [ "$HEAD_AFTER" = "$HEAD_BEFORE" ]; then
# No new commits — restore the original reviewer set.
USERS=${USERS_BEFORE:-[]}
TEAMS=${TEAMS_BEFORE:-[]}
if [ "$USERS" = "[]" ] && [ "$TEAMS" = "[]" ]; then
echo "Claude made no commits and no reviewers were originally requested."
exit 0
fi
echo "Claude made no commits; restoring original reviewers."
# Do NOT add `|| true` here. A failed restore means the human
# reviewer was silently dropped from the PR and never gets the
# post-Claude notification — fail loudly so it can be re-added
# manually instead of vanishing.
jq -n --argjson users "$USERS" --argjson teams "$TEAMS" \
'{reviewers: $users, team_reviewers: $teams}' \
| gh api -X POST \
"repos/$REPO/pulls/$PR_NUMBER/requested_reviewers" \
--input -
exit 0
fi
# Claude pushed commits: route the re-request to the PR's human
# assignees so they can review Claude's changes and delegate
# further reviews. Bot assignees (e.g. copilot-swe-agent) are
# filtered out.
ASSIGNEES=$(echo "$PR_JSON" | jq -c '[.assignees[] | select(.type == "User") | .login]')
echo "Human assignees: $ASSIGNEES"
if [ "$ASSIGNEES" = "[]" ]; then
echo "Claude added commits but PR has no human assignees; leaving reviewers cleared."
exit 0
fi
# Same no-`|| true` rule as above — failure here drops the human
# assignee silently, so let it surface.
jq -n --argjson users "$ASSIGNEES" '{reviewers: $users}' \
| gh api -X POST \
"repos/$REPO/pulls/$PR_NUMBER/requested_reviewers" \
--input -