diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 1e27862..15d69da 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -32,14 +32,14 @@ jobs: id-token: write steps: - - name: Record start time - run: echo "REVIEW_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" - - name: Checkout repository uses: actions/checkout@v7 with: fetch-depth: 1 + - name: Record start time + run: echo "REVIEW_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" + - name: Run Claude Code Review id: claude-review uses: anthropics/claude-code-action@v1 @@ -53,34 +53,52 @@ jobs: # runs and is discarded (the log doesn't even show it unless # show_full_output is enabled). prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }} --comment' - # Explicitly allow the posting tools (mirrors the plugin command's - # allowed-tools frontmatter). In headless mode the frontmatter grant - # doesn't reach the inline-comment MCP tool: PR #9's review hit 46 - # permission denials and posted nothing despite finding issues. - claude_args: | - --model claude-opus-5-5 --allowedTools "mcp__github_inline_comment__create_inline_comment,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:*)" --append-system-prompt "You are running non-interactively. Never run subagents in the background and never schedule wakeups: ending your turn ends the session. Wait for every reviewer to finish, then post the review comment before you stop." + show_full_output: true + # Explicitly allow the posting and reading tools. In headless mode the + # plugin's allowed-tools frontmatter doesn't reach them: PR #9's review + # hit 46 permission denials and posted nothing despite finding issues. + # + # The appended prompt keeps the review in the foreground. Headless runs + # often backgrounded the reviewer subagents and ended the turn to + # "wait" for them, which ends the session: PR #223's review stopped + # after 10 turns and posted nothing. Same fix as dcd's workflow. + claude_args: '--model claude-opus-5-5 --allowedTools "mcp__github_comment__update_claude_comment,mcp__github_inline_comment__create_inline_comment,Read,Glob,Grep,WebFetch,Bash(gh:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*)" --append-system-prompt "You are running non-interactively. Never run subagents in the background and never schedule wakeups: ending your turn ends the session. Wait for every reviewer to finish, then post the review comment before you stop. If you stop early because the PR is trivial or needs no review, still post one short PR comment that says so and why. Do not post anything if you stop because you already commented on this PR."' # 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 # The action exits green even when Claude ends its turn before posting - # anything (e.g. it backgrounded its reviewer subagents and stopped to - # "wait" for them). Fail loudly instead of leaving a silent green check. + # anything. Fail loudly instead of leaving a silent green check. + # + # The code-review plugin also skips, on purpose, any PR Claude has already + # commented on. So a push after the first review posts nothing, and that + # is not a failure. Only fail when Claude has never posted on the PR. + # + # The plugin also stops early on PRs it judges trivial. The prompt above + # tells Claude to post a one-line note in that case, so a deliberate skip + # leaves a comment and only a real failure trips this check. - name: Check a review comment was posted env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} PR: ${{ github.event.pull_request.number }} run: | - count=$( - { - gh api "repos/$REPO/issues/$PR/comments" --paginate \ - --jq ".[] | select(.user.login == \"claude[bot]\" and .created_at >= \"$REVIEW_STARTED_AT\") | .id" - gh api "repos/$REPO/pulls/$PR/comments" --paginate \ - --jq ".[] | select(.user.login == \"claude[bot]\" and .created_at >= \"$REVIEW_STARTED_AT\") | .id" - } | wc -l - ) - if [ "$count" -eq 0 ]; then + # Prints the time of every claude[bot] comment, inline comment and review. + claude_posts() { + gh api "repos/$REPO/issues/$PR/comments" --paginate \ + --jq '.[] | select(.user.login == "claude[bot]") | .created_at' + gh api "repos/$REPO/pulls/$PR/comments" --paginate \ + --jq '.[] | select(.user.login == "claude[bot]") | .created_at' + gh api "repos/$REPO/pulls/$PR/reviews" --paginate \ + --jq '.[] | select(.user.login == "claude[bot]") | .submitted_at' + } + posts=$(claude_posts) + new=$(printf '%s\n' "$posts" | awk -v since="$REVIEW_STARTED_AT" 'NF && $0 >= since' | wc -l | tr -d ' ') + total=$(printf '%s\n' "$posts" | awk 'NF' | wc -l | tr -d ' ') + if [ "$new" -gt 0 ]; then + echo "Claude posted $new comment(s)." + elif [ "$total" -gt 0 ]; then + echo "::notice::No new comment. Claude already reviewed this PR, and the code-review plugin reviews each PR once." + else echo "::error::Claude finished without posting a review comment on this PR." exit 1 fi - echo "Claude posted $count comment(s)."