diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index a8ce24b..d3c37ba 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -2,9 +2,8 @@ name: Claude Code Review on: pull_request: - types: [opened, synchronize, ready_for_review] + types: [opened, synchronize, reopened, ready_for_review] branches: [main] - workflow_dispatch: permissions: contents: read @@ -18,76 +17,73 @@ jobs: runs-on: ubuntu-latest if: >- ${{ - github.event_name == 'workflow_dispatch' || - ( - github.event.pull_request.draft == false && - contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association) - ) + github.event.pull_request.draft == false && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association) }} env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} - GH_TOKEN_VALUE: ${{ secrets.GH_TOKEN }} CLAUDE_MODEL: claude-sonnet-4-6 steps: - name: Skip when Claude secrets are not configured - if: ${{ env.ANTHROPIC_API_KEY == '' || env.ANTHROPIC_BASE_URL == '' || env.GH_TOKEN_VALUE == '' }} + if: ${{ env.ANTHROPIC_API_KEY == '' || env.ANTHROPIC_BASE_URL == '' }} run: echo "Claude Code review secrets are not configured; skipping Claude Code review." + + - name: Detect Claude review workflow changes + id: claude-workflow-change + if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' }} + uses: actions/github-script@v8 + with: + script: | + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + per_page: 100, + }); + const selfChanged = files.some( + (file) => file.filename === ".github/workflows/claude-code-review.yml", + ); + core.setOutput("self_changed", selfChanged ? "true" : "false"); + + - name: Skip Claude review for workflow self-change + if: ${{ steps.claude-workflow-change.outputs.self_changed == 'true' }} + run: echo "Skipping Claude Code Review because this PR changes the review workflow itself." + - name: Checkout repository - if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' && env.GH_TOKEN_VALUE != '' }} - uses: actions/checkout@v4 + if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' && steps.claude-workflow-change.outputs.self_changed != 'true' }} + uses: actions/checkout@v6 with: fetch-depth: 1 persist-credentials: false + - name: Run Claude Code review - id: claude-review - if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' && env.GH_TOKEN_VALUE != '' }} - continue-on-error: true + if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' && steps.claude-workflow-change.outputs.self_changed != 'true' }} uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.GH_TOKEN }} + track_progress: true + use_sticky_comment: true + exclude_comments_by_actor: MapleEve,github-actions,codecov,sourcery-ai,copilot-pull-request-reviewer prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + Review this pull request using REVIEW.md as the review-only guide. - Focus on actionable VoScript risks: privacy/security leaks, model lifecycle races, - GPU/CPU fallback behavior, HTTP API compatibility, regression-test coverage, and - synchronized English/Chinese documentation. Avoid formatting-only comments. + Focus on actionable VoScript risks: + - Privacy and security leaks + - Model lifecycle races and GPU/CPU fallback behavior + - HTTP API compatibility + - Regression-test coverage + - Synchronized English/Chinese documentation + + The PR branch is already checked out in the current working directory. + Post feedback only through the official Claude Code Action GitHub integration. + Do not use the GitHub CLI and do not use a user-owned GitHub token. + If the official Claude GitHub App integration is unavailable, fail instead of posting as the repository owner. + If there are no actionable findings, post the standard no-findings confirmation through the action integration. + Avoid formatting-only comments. + claude_args: | --model ${{ env.CLAUDE_MODEL }} --max-turns 30 - env: - ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} - - - name: Post Claude Code review summary - if: ${{ always() && github.event_name == 'pull_request' && env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' && env.GH_TOKEN_VALUE != '' }} - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - CLAUDE_OUTCOME: ${{ steps.claude-review.outcome }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - MARKER: "" - run: | - set -euo pipefail - short_sha="${HEAD_SHA:0:7}" - if [ "$CLAUDE_OUTCOME" = "success" ]; then - body="$(printf '%s\n### Claude Code Review\n\nClaude Code Review completed for `%s`.\n\nThis summary is posted even when Claude has no line-level findings. If no separate Claude inline comments are visible, there were no actionable line-level findings for this run.\n\nRun: %s' "$MARKER" "$short_sha" "$RUN_URL")" - else - body="$(printf '%s\n### Claude Code Review\n\nClaude Code Review did not complete successfully for `%s`.\n\nCheck the workflow run before merging. The check will remain failed so this cannot be missed.\n\nRun: %s' "$MARKER" "$short_sha" "$RUN_URL")" - fi - - comment_id="$( - gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate \ - --jq ".[] | select(.body | contains(\"$MARKER\")) | .id" | tail -n 1 - )" - if [ -n "$comment_id" ]; then - jq -n --arg body "$body" '{body: $body}' \ - | gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" --input - - else - jq -n --arg body "$body" '{body: $body}' \ - | gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --input - - fi - - - name: Fail when Claude Code review failed - if: ${{ steps.claude-review.outcome == 'failure' }} - run: exit 1