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 →
When CI fails on a pull request, Claude reads the failure logs, makes a fix, and pushes it to a new branch for you to review. The upstream file warns that it runs PR code with write permissions, so use it only on trusted branches.
workflow_run: CI completed (failure)
How it works
Setup
.github/workflows/claude-code-action-ci-failure-auto-fix.ymlGITHUB_TOKEN, ANTHROPIC_API_KEY. The file reads them 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 "CI failure auto-fix: Claude pushes a fix branch when CI fails" GitHub workflow from AgentFlows (https://agentflows.sh/flows/claude-code-action-ci-failure-auto-fix) so it runs on my Claude Pro or Max subscription instead of API credits.
Install these agent skills first. Download each SKILL.md from its source (respect its license) into .claude/skills/<name>/SKILL.md, or wherever this agent loads skills from:
- Debug: https://github.com/anthropics/knowledge-work-plugins/blob/main/engineering/skills/debug/SKILL.md (Apache-2.0), notes at https://agentflows.sh/skills/debug
- Iterate on a PR until CI passes and review feedback is addressed: https://github.com/getsentry/skills/tree/main/skills/iterate-pr (Apache-2.0), notes at https://agentflows.sh/skills/getsentry-iterate-pr
Save this file as .github/workflows/claude-code-action-ci-failure-auto-fix.yml:
```yaml
name: Auto Fix CI Failures
# ⚠️ SECURITY NOTE
#
# This workflow checks out the PR branch and runs build/test commands
# (npm, bun, etc.) against it with elevated permissions (contents:write,
# id-token:write). This means code from the PR branch executes in a
# trusted context with access to secrets and the ability to push to the
# repository.
#
# Only use this workflow in repositories where everyone with write access
# is fully trusted with these permissions. Do not use this in repositories
# that accept contributions from untrusted or semi-trusted collaborators.
#
# The pull_requests[0] check below limits this to same-repo PRs (fork PRs
# are excluded), but anyone who can push a branch to this repository can
# control what code runs here.
on:
workflow_run:
workflows: ["CI"]
types:
- completed
permissions:
contents: write
pull-requests: write
actions: read
issues: write
id-token: write # Required for OIDC token exchange
jobs:
auto-fix:
if: |
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.pull_requests[0] &&
!startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_branch }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup git identity
run: |
git config --global user.email "claude[bot]@users.noreply.github.com"
git config --global user.name "claude[bot]"
- name: Create fix branch
id: branch
env:
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
RUN_ID: ${{ github.run_id }}
run: |
SAFE_BRANCH=$(printf '%s' "$HEAD_BRANCH" | tr -cd 'a-zA-Z0-9/_.-')
BRANCH_NAME="claude-auto-fix-ci-${SAFE_BRANCH}-${RUN_ID}"
git checkout -b "$BRANCH_NAME"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
- name: Get CI failure details
id: failure_details
uses: actions/github-script@v7
with:
script: |
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }}
});
const jobs = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }}
});
const failedJobs = jobs.data.jobs.filter(job => job.conclusion === 'failure');
let errorLogs = [];
for (const job of failedJobs) {
const logs = await github.rest.actions.downloadJobLogsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
job_id: job.id
});
errorLogs.push({
jobName: job.name,
logs: logs.data
});
}
return {
runUrl: run.data.html_url,
failedJobs: failedJobs.map(j => j.name),
errorLogs: errorLogs
};
- name: Fix CI failures with Claude
id: claude
uses: anthropics/claude-code-action@v1
with:
prompt: |
/fix-ci
Failed CI Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }}
Failed Jobs: ${{ join(fromJSON(steps.failure_details.outputs.result).failedJobs, ', ') }}
PR Number: ${{ github.event.workflow_run.pull_requests[0].number }}
Branch Name: ${{ steps.branch.outputs.branch_name }}
Base Branch: ${{ github.event.workflow_run.head_branch }}
Repository: ${{ github.repository }}
Error logs:
${{ toJSON(fromJSON(steps.failure_details.outputs.result).errorLogs) }}
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(gh:*)'"
```
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: workflow_run: CI completed (failure). 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/claude-code-action-ci-failure-auto-fix.yml
Name
The name GitHub shows in the Actions tab. The file path is under Where to paste this.
name: Auto Fix CI Failures# ⚠️ SECURITY NOTE## This workflow checks out the PR branch and runs build/test commands# (npm, bun, etc.) against it with elevated permissions (contents:write,# id-token:write). This means code from the PR branch executes in a# trusted context with access to secrets and the ability to push to the# repository.## Only use this workflow in repositories where everyone with write access# is fully trusted with these permissions. Do not use this in repositories# that accept contributions from untrusted or semi-trusted collaborators.## The pull_requests[0] check below limits this to same-repo PRs (fork PRs# are excluded), but anyone who can push a branch to this repository can# control what code runs here.Trigger
GitHub starts a run when the workflow named CI finishes.
on: workflow_run: workflows: ["CI"] types: - completedPermissions
This token may push commits, comment on pull requests and open them, read Actions logs, and comment on issues and change labels. Anything not listed is not granted.
permissions: contents: write pull-requests: write actions: read issues: write id-token: write # Required for OIDC token exchangeJobs
Each job is one runner. The steps inside it run top to bottom.
jobs: auto-fix: if: | github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.pull_requests[0] && !startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-') runs-on: ubuntu-latestSteps
These steps run in order on that job.
steps: - name: Checkout code uses: actions/checkout@v6 with: ref: ${{ github.event.workflow_run.head_branch }} fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Setup git identity run: | git config --global user.email "claude[bot]@users.noreply.github.com" git config --global user.name "claude[bot]" - name: Create fix branch id: branch env: HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} RUN_ID: ${{ github.run_id }} run: | SAFE_BRANCH=$(printf '%s' "$HEAD_BRANCH" | tr -cd 'a-zA-Z0-9/_.-') BRANCH_NAME="claude-auto-fix-ci-${SAFE_BRANCH}-${RUN_ID}" git checkout -b "$BRANCH_NAME" echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" - name: Get CI failure details id: failure_details uses: actions/github-script@v7 with: script: | const run = await github.rest.actions.getWorkflowRun({ owner: context.repo.owner, repo: context.repo.repo, run_id: ${{ github.event.workflow_run.id }} }); const jobs = await github.rest.actions.listJobsForWorkflowRun({ owner: context.repo.owner, repo: context.repo.repo, run_id: ${{ github.event.workflow_run.id }} }); const failedJobs = jobs.data.jobs.filter(job => job.conclusion === 'failure'); let errorLogs = []; for (const job of failedJobs) { const logs = await github.rest.actions.downloadJobLogsForWorkflowRun({ owner: context.repo.owner, repo: context.repo.repo, job_id: job.id }); errorLogs.push({ jobName: job.name, logs: logs.data }); } return { runUrl: run.data.html_url, failedJobs: failedJobs.map(j => j.name), errorLogs: errorLogs }; - name: Fix CI failures with Claude id: claudeAgent
Runs Claude Code with anthropics/claude-code-action@v1. Inputs: prompt (the task); anthropic_api_key (the Claude API key secret); claude_args (Claude Code flags, including allowed tools).
uses: anthropics/claude-code-action@v1 with: prompt: | /fix-ci Failed CI Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }} Failed Jobs: ${{ join(fromJSON(steps.failure_details.outputs.result).failedJobs, ', ') }} PR Number: ${{ github.event.workflow_run.pull_requests[0].number }} Branch Name: ${{ steps.branch.outputs.branch_name }} Base Branch: ${{ github.event.workflow_run.head_branch }} Repository: ${{ github.repository }} Error logs: ${{ toJSON(fromJSON(steps.failure_details.outputs.result).errorLogs) }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(gh:*)'"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 caveatWeekly
Workflow changes, skill picks, and measured run costs. One email.