Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 39 additions & 21 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)."
Loading