Claude auto PR review
Copied from the official Solutions Guide basic example (prompt shortened).
pull_request: opened, synchronize
Est. cost per run~$0.18Checked Oct 16, 2026
Not executedJust shippedOur pick for the best security, implementation, and planning skills.Best skills for each job.See the picks →
Mention @claude in an issue, a PR comment or a review and Claude responds or makes the requested change. This is the Pulumi registry's production setup.
issue_comment / pull_request_review_comment containing @claude (+ issues: opened, assigned; reviews)
How it works
Setup
.github/workflows/registry-claude.ymlANTHROPIC_API_KEY. The file reads it as secrets.NAME. Do not paste the key into the YAML.Let your agent do it
Paste it into your coding agent in this repo. It does the setup and asks before anything leaves your machine.
Runs in GitHub Actions on your Claude Pro or Max plan through an OAuth token from claude setup-token. No API credits.
Set up the "Pulumi registry: @claude assistant on issues, PRs and reviews" GitHub workflow from AgentFlows (https://agentflows.sh/flows/registry-claude) so it runs on my Claude Pro or Max subscription instead of API credits.
Save this file as .github/workflows/registry-claude.yml:
```yaml
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
pull_request_review:
types: [submitted]
issues:
types: [opened, assigned]
concurrency:
group: claude-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false
jobs:
claude:
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'))
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Check user permission
id: check_permission
uses: actions/github-script@v9
with:
script: |
let actor;
if (context.eventName === 'issue_comment' || context.eventName === 'pull_request_review_comment') {
actor = context.payload.comment.user.login;
} else if (context.eventName === 'pull_request_review') {
actor = context.payload.review.user.login;
} else {
actor = context.payload.issue.user.login;
}
// Always allow whitelisted bots
const allowedBots = ['github-copilot[bot]'];
if (allowedBots.includes(actor)) {
core.setOutput('has_permission', 'true');
return;
}
try {
const { data: permissionData } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: actor,
});
const permission = permissionData.permission;
const hasPermission = ['write', 'maintain', 'admin'].includes(permission);
core.setOutput('has_permission', hasPermission.toString());
if (!hasPermission) {
core.info(`User ${actor} has '${permission}' permission, which is insufficient. Skipping.`);
}
} catch (error) {
core.info(`Failed to check permissions for ${actor}: ${error.message}. Skipping.`);
core.setOutput('has_permission', 'false');
}
- name: Checkout repository
if: steps.check_permission.outputs.has_permission == 'true'
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Run Claude Code
if: steps.check_permission.outputs.has_permission == 'true'
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
```
It authenticates with CLAUDE_CODE_OAUTH_TOKEN. Tell me to run `claude setup-token` in my own terminal to create it, then run `gh secret set CLAUDE_CODE_OAUTH_TOKEN` and let me paste it. Never write the token into a file.
It runs on: issue_comment / pull_request_review_comment containing @claude (+ issues: opened, assigned; reviews). Runs count against my subscription's usage limits, and GitHub Actions minutes still apply.
Show me the diff, then commit on a new branch and open a pull request.The file
Copy the file as-is. The notes beside each block explain it.
.github/workflows/registry-claude.yml
Name
The name GitHub shows in the Actions tab. The file path is under Where to paste this.
name: Claude CodeTrigger
GitHub starts a run when someone comments on a pull request review, someone comments on an issue, or an issue is opened or assigned.
on: issue_comment: types: [created] pull_request_review_comment: types: [created] pull_request_review: types: [submitted] issues: types: [opened, assigned]concurrency: group: claude-${{ github.event.issue.number || github.event.pull_request.number }} cancel-in-progress: falseJobs
Each job is one runner. The steps inside it run top to bottom.
jobs: claude: 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')) runs-on: ubuntu-latest timeout-minutes: 30Permissions
This token may push commits, comment on pull requests and open them, comment on issues and change labels, request a GitHub OIDC token, and read Actions logs. Anything not listed is not granted.
permissions: contents: write pull-requests: write issues: write id-token: write actions: readSteps
These steps run in order on that job.
steps: - name: Check user permission id: check_permission uses: actions/github-script@v9 with: script: | let actor; if (context.eventName === 'issue_comment' || context.eventName === 'pull_request_review_comment') { actor = context.payload.comment.user.login; } else if (context.eventName === 'pull_request_review') { actor = context.payload.review.user.login; } else { actor = context.payload.issue.user.login; } // Always allow whitelisted bots const allowedBots = ['github-copilot[bot]']; if (allowedBots.includes(actor)) { core.setOutput('has_permission', 'true'); return; } try { const { data: permissionData } = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: actor, }); const permission = permissionData.permission; const hasPermission = ['write', 'maintain', 'admin'].includes(permission); core.setOutput('has_permission', hasPermission.toString()); if (!hasPermission) { core.info(`User ${actor} has '${permission}' permission, which is insufficient. Skipping.`); } } catch (error) { core.info(`Failed to check permissions for ${actor}: ${error.message}. Skipping.`); core.setOutput('has_permission', 'false'); } - name: Checkout repository if: steps.check_permission.outputs.has_permission == 'true' uses: actions/checkout@v7 with: fetch-depth: 1 - name: Run Claude Code if: steps.check_permission.outputs.has_permission == 'true'Agent
Runs Claude Code with anthropics/claude-code-action@v1. Inputs: anthropic_api_key (the Claude API key secret).
uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}Keep going
Copied from the official Solutions Guide basic example (prompt shortened).
pull_request: opened, synchronize
Est. cost per run~$0.18Checked Oct 16, 2026
Not executed`track_progress: true` posts a checkbox tracking comment (per Solutions Guide).
pull_request: opened, synchronize, ready_for_review, reopened
Est. cost per run~$0.22Checked Oct 16, 2026
Not executedNo `prompt` → the action runs in tag/interactive mode and responds to the comment. `trigger_phrase` default is `@claude` (usage.md).
issue_comment / pull_request_review_comment containing @claude
Est. cost per run~$0.27Checked Oct 16, 2026
Not executed· 1 caveat