From 58d5799256273943ee5cf681b82a2b87c185c9d0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 14 Oct 2026 09:35:01 +0000 Subject: [PATCH] ci(claude-review): use built-in /code-review and assert it ran Replace the free-form prompt that was posting "No issues found" after a few seconds with the built-in high-effort review, full history, and a check that the run actually did work. Co-authored-by: Le He --- .github/workflows/claude-code-review.yml | 97 +++++++++++++++++------- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 8452b0f..5d4cf75 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -2,7 +2,7 @@ name: Claude Code Review on: pull_request: - types: [opened, synchronize] + types: [opened, synchronize, ready_for_review, reopened] # Optional: Only run on specific file changes # paths: # - "src/**/*.ts" @@ -10,48 +10,93 @@ on: # - "src/**/*.js" # - "src/**/*.jsx" +# A push while a review is still running replaces it, rather than stacking a +# second review of a diff that is already stale. +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' + # Drafts are reviewed once they are marked ready. Bot PRs (release-please) + # need no review and only queue up as action_required. + if: github.event.pull_request.draft == false && github.event.pull_request.user.type != 'Bot' runs-on: ubuntu-latest permissions: contents: read - pull-requests: read - issues: read + pull-requests: write + issues: write id-token: write + checks: read + statuses: read + actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: - fetch-depth: 1 + # Full history, so the review can diff, log and blame against the base + # branch instead of piecing the PR together from `gh pr diff`. + fetch-depth: 0 + + # - name: Trust workspace directory + # run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Run Claude Code Review id: claude-review uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - prompt: | - REPO: ${{ github.repository }} - PR NUMBER: ${{ github.event.pull_request.number }} - - Please review this pull request and provide feedback on: - - Code quality and best practices - - Potential bugs or issues - - Performance considerations - - Security concerns - - Test coverage - - Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. - - Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. - + # Print Claude's messages to the job log. Without it a run that stops + # early or skims gives no reason. + show_full_output: true + # anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + # The built-in /code-review, not the code-review@claude-code-plugins + # command. The plugin's procedure (eligibility check, four parallel + # reviewers, validators) was routinely skipped by its Sonnet main loop: + # it would skip the subagents the review steps describe, read a few + # line ranges of a diff too big for one tool result, and post + # "No issues found" after a few seconds. Since Claude Code 2.1.x the + # built-in takes a PR number, an effort level and --comment, and + # verifies its findings before posting them. + prompt: '/code-review high ${{ github.event.pull_request.number }} --comment' # 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 - claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' + additional_permissions: | + actions: read + claude_args: | + --allowedTools "Bash(gh:*),Bash(git:*),mcp__github_inline_comment__create_inline_comment" + # The action reports success whenever the SDK returns is_error: false, even + # if Claude never did anything (unresolved slash command, missing plugin, + # empty prompt). That failure mode hid behind months of green runs, so + # assert that work actually happened. + # + # Check cost as well as turns: a real review delegates to subagents and can + # legitimately report num_turns 0 while spending several dollars. Only a run + # that is zero on BOTH counts truly did nothing. + # + # A run that did work but never delegated is the quieter failure: one + # agent skimming the diff and calling it clean. That only warns, because a + # tiny diff may not need subagents. + - name: Assert the review actually ran + if: always() && steps.claude-review.conclusion == 'success' + run: | + file="${{ steps.claude-review.outputs.execution_file }}" + if [ -z "$file" ] || [ ! -f "$file" ]; then + echo "::warning::No execution file — the action exited before starting Claude. Expected on PRs that modify this workflow (the action requires it to match the default branch); investigate otherwise." + exit 0 + fi + result=$(jq -r '[.. | objects | select(.type == "result")] | last' "$file") + turns=$(jq -r '.num_turns // 0' <<<"$result") + cost=$(jq -r '.total_cost_usd // 0' <<<"$result") + delegations=$(jq -r '[.. | objects | select(.type == "tool_use" and (.name == "Agent" or .name == "Task" or .name == "Workflow"))] | length' "$file") + echo "Claude turns: $turns, cost: \$$cost, subagent launches: $delegations" + if [ "$turns" -eq 0 ] && [ "$(jq -rn --argjson c "$cost" '$c > 0')" = "false" ]; then + echo "::error::Claude did no work — 0 turns and \$0 spent. The review was a no-op (bad prompt or unresolved slash command)." + exit 1 + fi + if [ "$delegations" -eq 0 ]; then + echo "::warning::The review launched no subagents. On anything but a tiny diff that means one agent skimmed it, so treat a clean result with suspicion." + fi