name: Claude Code Review permissions: contents: read pull-requests: write on: pull_request: types: [opened, reopened, synchronize] workflow_dispatch: inputs: pr_number: description: 'Pull Request Number' required: true type: string jobs: code-review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get PR number id: pr-number run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT else echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT fi - name: Get PR details id: pr-details uses: actions/github-script@v7 with: script: | try { const { owner, repo } = context.repo; const prNumber = ${{ steps.pr-number.outputs.number }}; console.log(`Getting PR #${prNumber} from ${owner}/${repo}`); const pr = await github.rest.pulls.get({ owner: owner, repo: repo, pull_number: prNumber }); return { base_sha: pr.data.base.sha, head_sha: pr.data.head.sha, base_ref: pr.data.base.ref, head_ref: pr.data.head.ref }; } catch (error) { core.setFailed(`Failed to get PR details: ${error.message}`); throw error; } - name: Generate diff and prepare review id: prepare run: | # Configure git to fetch PR refs git config --local --add remote.origin.fetch "+refs/pull/*/head:refs/remotes/origin/pr/*" git fetch origin BASE_SHA="${{ fromJSON(steps.pr-details.outputs.result).base_sha }}" HEAD_SHA="${{ fromJSON(steps.pr-details.outputs.result).head_sha }}" echo "Comparing $BASE_SHA..$HEAD_SHA" # Create a temporary directory for our files mkdir -p /tmp/review # Get the full diff and store it git diff "$BASE_SHA" "$HEAD_SHA" > /tmp/review/changes.diff # Count total lines in diff TOTAL_LINES=$(wc -l < /tmp/review/changes.diff) echo "Total diff lines: $TOTAL_LINES" # Create filtered version cat /tmp/review/changes.diff | \ grep -v -E '(package-lock.json|yarn.lock|node_modules|\.md$|\.json$)' | \ grep -E '\.(js|ts|py|cpp|h|java|cs)$' > /tmp/review/filtered.diff || true if [ -s /tmp/review/filtered.diff ]; then DIFF_SIZE=$(wc -c < /tmp/review/filtered.diff) echo "Found $DIFF_SIZE bytes of relevant changes" echo "diff_size=$DIFF_SIZE" >> $GITHUB_OUTPUT # Base64 encode the diff to preserve newlines and special characters ENCODED_DIFF=$(base64 -w 0 /tmp/review/filtered.diff) echo "diff_content=$ENCODED_DIFF" >> $GITHUB_OUTPUT echo "Preview of changes:" head -n 5 /tmp/review/filtered.diff else echo "No relevant file changes found" echo "diff_size=0" >> $GITHUB_OUTPUT fi - name: Analyze code with Claude if: steps.prepare.outputs.diff_size != '0' id: analysis env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | # Decode the diff content echo "${{ steps.prepare.outputs.diff_content }}" | base64 -d > /tmp/review/decoded.diff DIFF_CONTENT=$(cat /tmp/review/decoded.diff) # Create request body REQUEST_BODY=$(jq -n \ --arg content "You are performing a code review. Please analyze this code diff and provide a thorough review that covers: 1. Potential conflicts with existing codebase 2. Code correctness and potential bugs 3. Security vulnerabilities or risks 4. Performance implications 5. Maintainability and readability issues 6. Adherence to best practices and coding standards 7. Suggestions for improvements For each issue found: - Explain the problem clearly - Rate the severity (Critical/High/Medium/Low) - Provide specific recommendations for fixes - Include code examples where helpful If no issues are found in a particular area, explicitly state that. Here is the code diff to review: \`\`\` $DIFF_CONTENT \`\`\`" \ '{ "model": "claude-3-sonnet-20240229", "max_tokens": 4096, "temperature": 0.7, "messages": [{ "role": "user", "content": $content }] }') # Make the API request echo "Sending request to Claude API..." RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \ -H "Content-Type: application/json" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -d "$REQUEST_BODY") if echo "$RESPONSE" | jq -e '.content[0].text' > /dev/null; then # Base64 encode the review to preserve formatting REVIEW=$(echo "$RESPONSE" | jq -r '.content[0].text' | base64 -w 0) echo "review=$REVIEW" >> $GITHUB_OUTPUT else echo "Error in Claude API response: $RESPONSE" exit 1 fi - name: Post review comment if: success() && steps.prepare.outputs.diff_size != '0' uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { owner, repo } = context.repo; const review = Buffer.from('${{ steps.analysis.outputs.review }}', 'base64').toString(); const prNumber = ${{ steps.pr-number.outputs.number }}; await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: `# Claude Code Review\n\n${review}` });