Skip to content

improve(development-codebase-tools): tune agent refactorer #1513

improve(development-codebase-tools): tune agent refactorer

improve(development-codebase-tools): tune agent refactorer #1513

name: Claude Code Review
# Automated PR code reviews using Claude AI
#
# This workflow provides:
# - Formal GitHub reviews (APPROVE/REQUEST_CHANGES/COMMENT)
# - Inline comments on specific lines of code
# - Auto-resolution of fixed issues on subsequent reviews
# - Patch-ID based caching to skip rebases (no duplicate reviews)
# - Iterative review tracking of previous comments
#
# TRIGGERS:
# - Automatic: On PR open, synchronize (push), or ready_for_review
# - Comment: Add a comment containing "@request-claude-review" to trigger a fresh review
# - Manual: Via workflow_dispatch with PR number and optional force_review flag
#
# TRIGGERING A NEW REVIEW WITHOUT CODE CHANGES:
# The easiest way is to add a comment on the PR containing "@request-claude-review".
# This bypasses the cache and forces a fresh review.
#
# Alternatively, use the manual workflow_dispatch trigger via the Actions tab or CLI:
# gh workflow run "Claude Code Review" -f pr_number=123
#
# NOTE: Re-requesting a review from github-actions[bot] via the GitHub UI
# does NOT work. GitHub does not fire events when re-requesting reviews
# from bot accounts. Use the comment trigger or manual dispatch instead.
#
# The review uses the default prompt from this repository's
# .github/prompts/default-pr-review.md
on:
pull_request:
types:
- opened
- synchronize
- ready_for_review
# Comment trigger: Add "@request-claude-review" to any PR comment to force a fresh review
# Works with both regular PR comments and inline review comments
issue_comment:
types: [created]
# Pull request review comments (inline comments on diffs)
pull_request_review_comment:
types: [created]
# Manual trigger for forcing a new review without code changes
workflow_dispatch:
inputs:
pr_number:
description: "Pull request number to review"
required: true
type: string
force_review:
description: "Force a full review even if the code hasn't changed (bypasses cache)"
required: false
type: boolean
default: true
# Ensure only one review runs at a time per PR
# New reviews wait for the current one to complete before starting
concurrency:
group: claude-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }}
cancel-in-progress: false
jobs:
# Pre-check job for automatic reviews to detect automated PRs
# Only runs for pull_request events (not manual/comment triggers)
check-automated:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
is_automated: ${{ steps.check.outputs.is_automated }}
category: ${{ steps.check.outputs.category }}
skip_reason: ${{ steps.check.outputs.skip_reason }}
steps:
- uses: bullfrogsec/bullfrog@1831f79cce8ad602eef14d2163873f27081ebfb3 # v0.8.4
- name: Checkout repository (for composite action)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
sparse-checkout: .github/actions
sparse-checkout-cone-mode: false
- name: Check for automated PR
id: check
uses: ./.github/actions/check-automated-pr
with:
branch_name: ${{ github.head_ref }}
pr_title: ${{ github.event.pull_request.title }}
# Fetch PR details for manual triggers (workflow_dispatch) and comment triggers
# This job retrieves base_ref which isn't available in these event contexts
get-pr-info:
if: github.event_name == 'workflow_dispatch' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
base_ref: ${{ steps.pr-info.outputs.base_ref }}
head_ref: ${{ steps.pr-info.outputs.head_ref }}
pr_number: ${{ steps.pr-info.outputs.pr_number }}
should_run: ${{ steps.pr-info.outputs.should_run }}
steps:
- uses: bullfrogsec/bullfrog@1831f79cce8ad602eef14d2163873f27081ebfb3 # v0.8.4
- name: Get PR Info
id: pr-info
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
PR_NUMBER_FROM_REVIEW: ${{ github.event.pull_request.number }}
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
IS_PR: ${{ github.event.issue.pull_request && 'true' || 'false' }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
# Determine PR number based on event type
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
PR_NUMBER="$INPUT_PR_NUMBER"
SHOULD_RUN="true"
elif [ "$EVENT_NAME" = "pull_request_review_comment" ]; then
PR_NUMBER="$PR_NUMBER_FROM_REVIEW"
# Ignore comments from github-actions[bot] to prevent self-triggering
if [ "$COMMENT_AUTHOR" = "github-actions[bot]" ]; then
echo "ℹ️ Ignoring comment from github-actions[bot] to prevent self-triggering"
echo "should_run=false" >> $GITHUB_OUTPUT
exit 0
fi
# Check if comment contains the trigger phrase
if echo "$COMMENT_BODY" | grep -qi "@request-claude-review"; then
SHOULD_RUN="true"
echo "🔍 Found @request-claude-review in review comment"
else
SHOULD_RUN="false"
echo "ℹ️ Comment does not contain @request-claude-review, skipping"
fi
elif [ "$EVENT_NAME" = "issue_comment" ]; then
# issue_comment fires for both issues and PRs
# Ignore comments from github-actions[bot] to prevent self-triggering
if [ "$COMMENT_AUTHOR" = "github-actions[bot]" ]; then
echo "ℹ️ Ignoring comment from github-actions[bot] to prevent self-triggering"
echo "should_run=false" >> $GITHUB_OUTPUT
exit 0
fi
# Check if this is a PR (has pull_request field)
if [ "$IS_PR" != "true" ]; then
echo "ℹ️ Comment is on an issue, not a PR. Skipping."
echo "should_run=false" >> $GITHUB_OUTPUT
exit 0
fi
PR_NUMBER="$ISSUE_NUMBER"
# Check if comment contains the trigger phrase
if echo "$COMMENT_BODY" | grep -qi "@request-claude-review"; then
SHOULD_RUN="true"
echo "🔍 Found @request-claude-review in PR comment"
else
SHOULD_RUN="false"
echo "ℹ️ Comment does not contain @request-claude-review, skipping"
fi
fi
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "should_run=$SHOULD_RUN" >> $GITHUB_OUTPUT
if [ "$SHOULD_RUN" != "true" ]; then
echo "ℹ️ Skipping - trigger condition not met"
exit 0
fi
echo "ℹ️ Fetching PR #$PR_NUMBER info..."
PR_DATA=$(gh api repos/${GITHUB_REPOSITORY}/pulls/$PR_NUMBER)
BASE_REF=$(echo "$PR_DATA" | jq -r '.base.ref')
HEAD_REF=$(echo "$PR_DATA" | jq -r '.head.ref')
echo "base_ref=$BASE_REF" >> $GITHUB_OUTPUT
echo "head_ref=$HEAD_REF" >> $GITHUB_OUTPUT
echo "✅ PR #$PR_NUMBER: base=$BASE_REF, head=$HEAD_REF"
# Automatic review triggered by pull_request events (opened, synchronize, ready_for_review)
# Skips certain automated PRs but reviews dependency updates (for auto-merge support)
# Skip categories: release, sync, action-update, merge-queue
# Review categories: deps (dependabot/renovate), and all non-automated PRs
claude-review-auto:
needs: check-automated
if: |
github.event_name == 'pull_request' &&
(
needs.check-automated.outputs.is_automated != 'true' ||
needs.check-automated.outputs.category == 'deps'
) &&
(
github.event.pull_request.draft == false ||
github.event.action == 'ready_for_review' ||
github.event.pull_request.user.login == 'claude[bot]'
)
permissions:
id-token: write
contents: write
pull-requests: write
issues: read
actions: read
uses: ./.github/workflows/_claude-code-review.yml
with:
pr_number: ${{ github.event.pull_request.number }}
base_ref: ${{ github.base_ref }}
force_review: false
max_diff_lines: ${{ fromJSON(vars.MAX_DIFF_LINES) }}
# Use Opus 4.6 for comprehensive reviews with reliable inline comments
# Opus is more capable at following complex multi-step instructions
# Uncomment one of these to use a different model for reviews:
# model: 'claude-haiku-4-5'
# model: 'claude-sonnet-4-6'
model: "claude-opus-4-6"
# Allow standard review conversation turns
# max_turns: 15
# Standard timeout for most PRs
timeout_minutes: 20
# Optional: Restrict tools for read-only reviews (uncomment to enable)
# allowed_tools: 'Read,Grep,Glob,Bash(git log),Bash(git diff),Bash(git show)'
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
WORKFLOW_PAT: ${{ secrets.WORKFLOW_PAT }}
# Summary job for skipped automatic reviews
review-skipped:
needs: check-automated
if: |
github.event_name == 'pull_request' &&
needs.check-automated.outputs.is_automated == 'true' &&
needs.check-automated.outputs.category != 'deps'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: bullfrogsec/bullfrog@1831f79cce8ad602eef14d2163873f27081ebfb3 # v0.8.4
- name: Report skipped review
env:
SKIP_REASON: ${{ needs.check-automated.outputs.skip_reason }}
run: |
echo "::notice::Code review skipped for automated PR: $SKIP_REASON"
echo "✅ Automated PR detected - code review is not required for this PR type."
# Manual review triggered by workflow_dispatch
# This runs after get-pr-info to have access to PR metadata
# Note: Manual triggers bypass automated PR checks - if you explicitly request a review, you get one
claude-review-manual:
needs: get-pr-info
if: |
github.event_name == 'workflow_dispatch' &&
needs.get-pr-info.outputs.should_run == 'true'
permissions:
id-token: write
contents: write
pull-requests: write
issues: read
actions: read
uses: ./.github/workflows/_claude-code-review.yml
with:
pr_number: ${{ needs.get-pr-info.outputs.pr_number }}
base_ref: ${{ needs.get-pr-info.outputs.base_ref }}
force_review: ${{ inputs.force_review }}
max_diff_lines: ${{ fromJSON(vars.MAX_DIFF_LINES) }}
# Use Opus 4.6 for comprehensive reviews with reliable inline comments
model: "claude-opus-4-6"
# Standard timeout for most PRs
timeout_minutes: 20
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
WORKFLOW_PAT: ${{ secrets.WORKFLOW_PAT }}
# Comment-triggered review: Add "@request-claude-review" to any PR comment
# This forces a fresh review, bypassing the cache
# Note: Comment triggers bypass automated PR checks - if you explicitly request a review, you get one
claude-review-comment:
needs: get-pr-info
if: |
(github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
needs.get-pr-info.outputs.should_run == 'true'
permissions:
id-token: write
contents: write
pull-requests: write
issues: read
actions: read
uses: ./.github/workflows/_claude-code-review.yml
with:
pr_number: ${{ needs.get-pr-info.outputs.pr_number }}
base_ref: ${{ needs.get-pr-info.outputs.base_ref }}
force_review: true # Always force when triggered by comment
max_diff_lines: ${{ fromJSON(vars.MAX_DIFF_LINES) }}
# Use Opus 4.6 for comprehensive reviews with reliable inline comments
model: "claude-opus-4-6"
# Standard timeout for most PRs
timeout_minutes: 20
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
WORKFLOW_PAT: ${{ secrets.WORKFLOW_PAT }}
# Auto-merge Dependabot security updates after successful review
# Only runs for pull_request events (dependabot only triggers pull_request)
auto-merge-dependabot:
needs: claude-review-auto
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.user.login == 'dependabot[bot]' &&
(contains(github.event.pull_request.title, 'security') || contains(github.event.pull_request.title, 'Bump')) &&
needs.claude-review-auto.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: bullfrogsec/bullfrog@1831f79cce8ad602eef14d2163873f27081ebfb3 # v0.8.4
- name: Enable auto-merge for Dependabot updates
run: gh pr merge --auto --squash "$PR_NUMBER"
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}