Skip to content
Merged
Show file tree
Hide file tree
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
24 changes: 23 additions & 1 deletion .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
name: PR Review

# Auto-review only for SAME-REPO pull requests.
#
# Why not fork PRs: anthropics/claude-code-action cannot authenticate on a fork
# PR by any trigger. `on: pull_request` from a fork gets no OIDC token; switching
# to `on: pull_request_target` makes secrets available but then fails at the
# OIDC -> GitHub App token exchange (401 "Invalid OIDC token"). Either way a fork
# PR can never produce a green review check — it only burns a run (and, with
# pull_request_target, exposes secrets to fork code).
#
# So this workflow uses the safe `pull_request` event and the job is gated to
# same-repo PRs (head.repo == this repo). Result:
# - same-repo PRs (trusted, ours): reviewed automatically (OIDC works);
# - fork PRs: the job is SKIPPED — no failing check, zero token spend, no
# secret exposure. Review a fork PR on demand by commenting `@claude`
# (claude.yml, gated to OWNER/MEMBER/COLLABORATOR).
on:
pull_request:
types: [opened]
Expand All @@ -12,6 +27,12 @@ permissions:

jobs:
review:
# Same-repo PRs only. Fork PRs (head.repo != this repo) are skipped before any
# token is spent — see the header for why fork PRs can't be reviewed in CI.
# Also skip Dependabot (isolated secret store -> empty token -> always fails).
if: >-
github.event.pull_request.head.repo.full_name == github.repository
&& github.event.pull_request.user.login != 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -20,6 +41,7 @@ jobs:
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
allowed_bots: "dependabot"
prompt: |
REPO: ${{ github.repository }}
PR: #${{ github.event.pull_request.number }}
Expand All @@ -30,7 +52,7 @@ jobs:
- Security implications
- Performance concerns

The PR branch is checked out in the working directory.
Read the PR's changes via `gh pr diff` and `gh pr view`.

Use `gh pr comment` for top-level summary feedback (one comment max).
Use mcp__github_inline_comment__create_inline_comment (with confirmed: true)
Expand Down
38 changes: 33 additions & 5 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,40 @@ permissions:

jobs:
claude:
# Trust gate (load-bearing): this job runs from the default branch WITH secrets,
# contents: write, and a checkout of the PR head — comment/issue events are NOT
# protected by the fork-PR "require approval" gate, so without this check ANY
# non-bot user who types "@claude" could trigger a privileged, write-capable run.
#
# The association MUST be checked against the actor of the *firing* event — the
# commenter for *_comment, the reviewer for pull_request_review, the issue
# author for issues. An issue_comment payload carries BOTH `comment` and
# `issue`, so a blanket OR across them lets an untrusted commenter pass via the
# trusted issue author on a maintainer-owned issue/PR. So each event clause
# binds its own actor's association inline.
if: |
github.event.sender.type != 'Bot' && (
(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.sender.type != 'Bot'
&& (
(github.event_name == 'issue_comment'
&& contains(github.event.comment.body, '@claude')
&& (github.event.comment.author_association == 'OWNER'
|| github.event.comment.author_association == 'MEMBER'
|| github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review_comment'
&& contains(github.event.comment.body, '@claude')
&& (github.event.comment.author_association == 'OWNER'
|| github.event.comment.author_association == 'MEMBER'
|| github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review'
&& contains(github.event.review.body, '@claude')
&& (github.event.review.author_association == 'OWNER'
|| github.event.review.author_association == 'MEMBER'
|| github.event.review.author_association == 'COLLABORATOR')) ||
(github.event_name == 'issues'
&& (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))
&& (github.event.issue.author_association == 'OWNER'
|| github.event.issue.author_association == 'MEMBER'
|| github.event.issue.author_association == 'COLLABORATOR'))
)
runs-on: ubuntu-latest
steps:
Expand Down
Loading