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
40 changes: 34 additions & 6 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Claude Code Review

on:
pull_request:
types: [opened, ready_for_review]
issue_comment:
types: [created]
pull_request_review_comment:
Expand All @@ -10,13 +12,33 @@ on:
pull_request_review:
types: [submitted]

# One review per PR at a time. A rapid draft->ready->draft toggle would otherwise
# stack runs and burn Claude usage on states nobody is waiting for.
concurrency:
group: claude-review-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }}
cancel-in-progress: true

jobs:
claude:
# Defense-in-depth author gate: only run when the triggering actor is a repo
# OWNER/MEMBER/COLLABORATOR, so a drive-by comment or a fork PR from an
# outside account cannot invoke Claude or drain Claude usage. This repo is
# public, so the gate is load-bearing, not decorative.
#
# The pull_request arm reviews every PR opened by an org member — the
# compensating control for CC8.1, since a sole maintainer cannot approve
# their own PR. It is `pull_request`, never `pull_request_target`, so a fork
# PR carries no secrets even in the case where it somehow ran.
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
(
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
) ||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association))
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
Expand All @@ -41,8 +63,14 @@ jobs:
additional_permissions: |
actions: read

# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# On the automatic pull_request trigger there is no comment to act on, so
# supply the review brief. Left empty for comment-driven events so Claude
# follows the instructions in the comment that tagged it.
#
# Deliberately does NOT approve: org policy sets
# can_approve_pull_request_reviews=false, and an unconditional bot approval
# on every PR is a rubber stamp - worse audit evidence than none.
prompt: ${{ github.event_name == 'pull_request' && 'Review this pull request. This repository is a published SDK on a post-1.0 semver contract with external integrators, maintained by a single engineer who cannot approve their own PRs, so this review is the compensating change-management control - be substantive rather than cursory. Focus on correctness bugs, whether anything touches the stable tier (SDK facades, root exports, error classes, auth config, and the symbols the integration template imports) which would require a coordinated client major, and whether the PR description matches the diff. Post your findings as a comment. Do not approve the pull request.' || '' }}

# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
Expand Down
Loading