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
6 changes: 5 additions & 1 deletion .github/workflows/api-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ jobs:
contents: write
pull-requests: write
issues: write
# claude-code-action mints an OIDC token to authenticate to the
# Anthropic GitHub App. Required by the action itself. Safe here
# because api-sync only runs on schedule / workflow_dispatch, never
# on an untrusted trigger.
id-token: write

steps:
Expand Down Expand Up @@ -231,7 +235,7 @@ jobs:
} >> "$GITHUB_OUTPUT"

- name: Implement with Claude
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@51ea8ea73a139f2a74ff649e3092c25a904aed7e # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--allowedTools Bash,Read,Glob,Grep,Edit,Write"
Expand Down
32 changes: 23 additions & 9 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@ on:

jobs:
claude-review:
if: ${{ !github.event.pull_request.draft && github.event.pull_request.user.login != 'dependabot[bot]' }}
# Only review same-repo PRs. A fork PR runs attacker-controlled code in
# this job, and the review prompt reads files from the checked-out tree
# (CLAUDE.md etc), so a hostile fork could prompt-inject the agent while
# CLAUDE_CODE_OAUTH_TOKEN sits in the environment with Bash enabled.
if: >-
${{ !github.event.pull_request.draft &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.head.repo.fork == false }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
# claude-code-action mints an OIDC token to authenticate to the
# Anthropic GitHub App. Required by the action itself. Safe here
# because the job is gated to same-repo PRs (head.repo.fork == false),
# so the token is never reachable from an untrusted fork PR.
id-token: write

steps:
Expand All @@ -21,7 +32,10 @@ jobs:
fetch-depth: 0

- name: Run Claude Code Review
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@51ea8ea73a139f2a74ff649e3092c25a904aed7e # v1
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
use_sticky_comment: true
Expand All @@ -34,33 +48,33 @@ jobs:

```bash
# Dismiss all previous claude[bot] reviews
gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews" \
gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
--paginate --jq '.[] | select(.user.login == "claude[bot]" and (.state == "CHANGES_REQUESTED" or .state == "COMMENTED")) | .id' \
| while read -r review_id; do
gh api -X PUT "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${review_id}/dismissals" \
gh api -X PUT "repos/${REPO}/pulls/${PR_NUMBER}/reviews/${review_id}/dismissals" \
-f message="Superseded by new review" -f event="DISMISS" 2>/dev/null || true
done

# Delete all previous claude[bot] inline review comments
gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments" \
gh api "repos/${REPO}/pulls/${PR_NUMBER}/comments" \
--paginate --jq '.[] | select(.user.login == "claude[bot]") | .id' \
| while read -r comment_id; do
gh api -X DELETE "repos/${{ github.repository }}/pulls/comments/${comment_id}" 2>/dev/null || true
gh api -X DELETE "repos/${REPO}/pulls/comments/${comment_id}" 2>/dev/null || true
done

# Delete all previous claude[bot] issue comments (except sticky)
gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--paginate --jq '.[] | select(.user.login == "claude[bot]" and (.body | test("<!-- claude-code-action") | not)) | .id' \
| while read -r comment_id; do
gh api -X DELETE "repos/${{ github.repository }}/issues/comments/${comment_id}" 2>/dev/null || true
gh api -X DELETE "repos/${REPO}/issues/comments/${comment_id}" 2>/dev/null || true
done
```

After cleanup, proceed with the review below.

---

Review PR #${{ github.event.pull_request.number }} in ${{ github.repository }}.
Review PR #${PR_NUMBER} in ${REPO}.

Read `CLAUDE.md` for project conventions. Pay special attention to:
- The **CLI Design Principles** section (verb vocabulary, argument rules,
Expand Down
24 changes: 19 additions & 5 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,30 @@ on:

jobs:
claude:
# The actor must be a trusted member of the repo (OWNER / MEMBER /
# COLLABORATOR). Without this gate, any GitHub user can trigger an agent
# with Bash + a write-scoped token by commenting "@claude" on this public
# repo.
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 == '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
permissions:
contents: write
pull-requests: write
issues: write
# claude-code-action mints an OIDC token to authenticate to the
# Anthropic GitHub App. Required by the action itself, not optional.
id-token: write
actions: read

Expand All @@ -32,7 +46,7 @@ jobs:
fetch-depth: 1

- name: Run Claude Code
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@51ea8ea73a139f2a74ff649e3092c25a904aed7e # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--allowedTools Bash,Read,Glob,Grep,Edit,Write"
Expand Down
Loading