Skip to content

CI/CD Integration & Automation

CI/CD integration puts Claude Code inside a pipeline instead of an interactive session, driven by the anthropics/claude-code-action@v1 GitHub Action and headless mode (claude -p). Automated pull request review, issue triage, build repair, and nightly documentation all hang off events a team already reacts to — comments, labels, failed runs, cron — against the Anthropic API, AWS Bedrock, or Google Vertex AI.

Thirty pull requests a week, each waiting hours for a human first pass. Half the review comments are the same patterns every time: missing error handling, inconsistent naming, tests that skip the edge cases. Meanwhile flaky tests get rubber-stamped, a security regression slips through, and the docs drift further from the code every night.

More reviewers is not the answer. An automated first pass that runs on every PR is, and it is the point where Claude Code stops being an interactive tool and becomes part of the pipeline.

What CI/CD automation with Claude Code gives you

Section titled “What CI/CD automation with Claude Code gives you”
  • A working @claude workflow that responds to PR and issue comments
  • An AI PR-review job on every pull request, using the built-in /review command or your own security-focused prompt
  • An auto-fix job that reads failure logs and pushes a fix without papering over a real regression
  • Scheduled automations: a daily summary, issue auto-labeling, and a nightly documentation PR
  • Bedrock and Vertex AI configurations for enterprise environments
  • A cost-gating trigger that only invokes Claude on substantial diffs

The fastest path runs the installer from inside Claude Code.

  1. Launch the REPL and run the installer

    Run claude to open the interactive REPL, then enter the slash command /install-github-app. It walks you through installing the GitHub app and creating the required secrets.

  2. Follow the prompts

    Authorize the Claude GitHub App, grant repository permissions, and let it configure the API key. The app needs Contents, Issues, and Pull requests at Read & Write.

  3. Test the integration

    Create an issue comment that mentions Claude:

    @claude implement this feature based on the issue description

Claude reads the surrounding context — issue description, PR diff, conversation history — and answers with code, an explanation, or direct changes. The same mention works for @claude fix the TypeError in the dashboard component or an open-ended @claude how should I approach refactoring the auth middleware?.

Wiring the action by hand, including Bedrock and Vertex

Section titled “Wiring the action by hand, including Bedrock and Vertex”

Manual setup is what you need for custom configurations and cloud providers. Add your key as a repository secret named ANTHROPIC_API_KEY, then create the workflow file.

.github/workflows/claude.yml
name: Claude Code Actions
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened]
permissions:
contents: write
pull-requests: write
issues: write
jobs:
claude-pr:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
timeout-minutes: 60 # Job-level clock, not an action input
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
trigger_phrase: '@claude'
claude_args: '--max-turns 30'

What the cloud-provider tabs get wrong when you improvise

Section titled “What the cloud-provider tabs get wrong when you improvise”

AWS_REGION is a required environment variable for Bedrock. The configure-aws-credentials action exports it from aws-region, but setting it explicitly on the job keeps the configuration robust when someone later reorders the steps.

For Vertex, the action has no vertex_region or vertex_project_id inputs. Region and project are supplied through the CLOUD_ML_REGION and ANTHROPIC_VERTEX_PROJECT_ID environment variables, and the project is auto-derived from the google-github-actions/auth step output when you wire it through, as above.

Reviewing every pull request automatically

Section titled “Reviewing every pull request automatically”

/review is a prebuilt command, and the action posts its findings as review comments on the PR. This is the cheapest useful automation in the list:

.github/workflows/claude-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for better analysis
- uses: anthropics/claude-code-action@v1
with:
# /review is a built-in command; the action posts review comments to the PR.
prompt: '/review'
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_args: '--max-turns 5'

A security-focused review with your own instructions

Section titled “A security-focused review with your own instructions”

The built-in command handles the common cases. When you want a security-first pass with a fixed output format, inline your own instructions in the prompt field — there is no prompt_file input.

The severity vocabulary matters more than it looks. A review that flags everything at the same volume gets ignored by week two, so make the prompt rank its findings and say explicitly when it found nothing serious.

Label an issue and let the pipeline open the PR:

.github/workflows/issue-to-pr.yml
name: Issue to PR
on:
issues:
types: [labeled]
jobs:
implement-feature:
if: github.event.label.name == 'implement-with-claude'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
prompt: |
Implement the feature described in issue #${{ github.event.issue.number }}:
${{ github.event.issue.title }}
${{ github.event.issue.body }}
Follow our coding standards in CLAUDE.md.
Create comprehensive tests.
Update documentation as needed.
When the change is complete, open a pull request titled
"feat: ${{ github.event.issue.title }}" and reference this issue in the body.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_args: '--max-turns 30'

The label is the gate. Nothing runs until a human decides the issue is well-specified enough to hand over, which is the difference between useful automation and a bot that opens twelve PRs against a one-line bug report.

Fixing a failing build without a human first

Section titled “Fixing a failing build without a human first”
.github/workflows/fix-ci.yml
name: Auto-fix CI Failures
on:
workflow_run:
workflows: ['CI']
types: [completed]
jobs:
fix-failures:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_branch }}
- name: Get failure logs
uses: actions/github-script@v7
id: logs
with:
script: |
// workflow_run.id is a RUN id, so use downloadWorkflowRunLogs (run_id),
// not downloadJobLogsForWorkflowRun (which expects a job_id).
const logs = await github.rest.actions.downloadWorkflowRunLogs({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }}
});
return logs.data;
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_args: '--max-turns 20'
prompt: |
The CI build failed with these errors:
${{ steps.logs.outputs.result }}
Fix the issues causing the build to fail.
Focus on test failures, linting errors, and type errors.
Commit the fix to the current branch with the message "fix: resolve CI failures".

That prompt is the naive version, and the naive version will eventually delete a test to make the build green. Harden it:

name: Daily Report
on:
schedule:
- cron: "0 9 * * 1-5" # 9 AM weekdays
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Generate a summary of the last 24 hours:
1. List all merged PRs with a one-line description
2. List all open issues created in the last 24 hours
3. Highlight any CI failures on the main branch
Create this as a comment on issue #1 (our daily log).
claude_args: "--model sonnet --max-turns 5"

Auto-fixing lint and type errors on new PRs

Section titled “Auto-fixing lint and type errors on new PRs”
name: Auto-Fix
on:
pull_request:
types: [opened]
jobs:
fix:
runs-on: ubuntu-latest
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Run the linter and fix any issues. If there are type errors,
fix those too. Commit the changes with a clear message.
claude_args: "--max-turns 15"

This one runs headless rather than through the action, because it chains several passes and then hands the result to a PR-creating action:

.github/workflows/update-docs.yml
name: Update Documentation
on:
schedule:
- cron: '0 2 * * *' # 2 AM daily
workflow_dispatch:
jobs:
update-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update API Documentation
run: |
claude -p "Update API documentation in docs/api.md based on current code in src/api/" \
--allowedTools "Edit" "Read" \
--output-format json > result.json
- name: Update README
run: |
claude -p "Update README.md badges, dependencies list, and examples based on package.json and recent changes" \
--allowedTools "Edit" "Read"
- name: Create PR if changes
uses: peter-evans/create-pull-request@v7
with:
title: 'docs: automated documentation updates'
commit-message: 'docs: update API docs and README'
branch: auto-update-docs

Driving Claude from a script with headless mode

Section titled “Driving Claude from a script with headless mode”

claude -p is the same agent without the REPL. It takes a prompt, honors --allowedTools, and can emit JSON for a script to parse:

Terminal window
# Simple one-shot command
claude -p "Update all copyright headers to 2026" --output-format json
# With specific permissions
claude -p "Fix the failing test in auth.test.js" \
--allowedTools "Edit" "Read" "Bash" \
--output-format json
# Pipe data for processing
cat error.log | claude -p "Analyze these errors and suggest fixes"

With cat file | claude -p, stdin becomes the prompt, so no positional argument is needed. That is the idiom behind the official one-liner gh pr diff "$1" | claude -p --append-system-prompt "..." --output-format json, and it is what makes long, multi-line prompts practical in a shell script.

One planning pass produces the list; a loop runs a scoped pass per file. This keeps each call’s context small, which is both cheaper and more accurate than one enormous migration prompt:

migrate-components.sh
#!/bin/bash
# Generate task list
claude -p "List all React class components that need hooks migration" \
--output-format json > tasks.json
# Process each component
jq -r '.files[]' tasks.json | while read file; do
echo "Migrating $file..."
claude -p "Convert $file from class component to hooks. Preserve all functionality." \
--allowedTools "Edit"
done
Terminal window
# Code quality pipeline
npm run lint 2>&1 | \
claude -p "Fix all linting errors" --allowedTools "Edit" | \
claude -p "Now run tests and fix any failures" --allowedTools "Bash" "Edit" | \
claude -p "Generate a summary of changes" > changes.md
.git/hooks/pre-commit
#!/bin/bash
# Check for TODO comments
if git diff --cached --name-only | xargs grep -l "TODO" > /dev/null; then
echo "Found TODO comments. Asking Claude to address them..."
git diff --cached --name-only | xargs grep -l "TODO" | while read file; do
claude -p "In $file, implement any TODO comments or convert them to proper issues" \
--allowedTools "Edit"
done
# Re-stage changes
git add -u
fi

Coordinating a change across microservices

Section titled “Coordinating a change across microservices”

A plan job produces the service list, then a matrix job runs the same change in each repository:

.github/workflows/coordinated-update.yml
name: Coordinated Service Update
on:
workflow_dispatch:
inputs:
change_description:
description: 'Describe the change to implement'
required: true
jobs:
plan:
runs-on: ubuntu-latest
outputs:
plan: ${{ steps.create-plan.outputs.plan }}
steps:
- uses: actions/checkout@v4
- id: create-plan
run: |
PLAN=$(claude -p "Create an implementation plan for: ${{ github.event.inputs.change_description }}. List affected services and order of updates." --output-format json)
echo "plan=$PLAN" >> $GITHUB_OUTPUT
update-services:
needs: plan
strategy:
matrix:
service: ${{ fromJson(needs.plan.outputs.plan).services }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: myorg/${{ matrix.service }}
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_args: '--max-turns 30'
prompt: |
Implement this change: ${{ github.event.inputs.change_description }}
This is service: ${{ matrix.service }}
Full plan: ${{ needs.plan.outputs.plan }}
Ensure backward compatibility, then open a pull request against this service's
default branch describing the change and its place in the overall plan.

Security and compliance scans in the pipeline

Section titled “Security and compliance scans in the pipeline”

Let the existing scanners produce the findings, and let Claude do the triage and the write-up:

.github/workflows/security-scan.yml
name: Security Analysis
on:
pull_request:
branches: [main]
jobs:
security-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Security Scan
run: |
npm audit --json > audit.json
bandit -r . -f json -o bandit.json || true
- name: Analyze and Fix
run: |
claude -p "Analyze security reports and fix critical issues:
NPM Audit: $(cat audit.json)
Bandit: $(cat bandit.json)
Fix only CRITICAL and HIGH severity issues.
Document any issues that require manual review." \
--allowedTools "Edit" "Read"
- name: Generate Security Report
run: |
claude -p "Generate a security assessment report based on the changes made" \
> security-report.md

Keep the API key out of logs and artifacts, give the CI key its own spending limit, and keep Claude’s permissions to the minimum the job needs. Automated changes still go through human review before they deploy.

Claude Code runs on GitHub-hosted runners, so every job consumes Actions minutes as well as API tokens. Four controls do most of the work:

  • Cap the turns. --max-turns in claude_args prevents a runaway job. Five to ten turns is enough for most reviews.
  • Select the model explicitly. Account defaults differ, so pass --model claude-sonnet-5 in CI when you want its lower cost instead of relying on the runtime default.
  • Cap the clock. A job-level timeout-minutes: stops an infinite loop; there is no timeout_minutes action input.
  • Limit parallel runs. A concurrency group per PR cancels the superseded run when someone pushes three times in a row.
jobs:
claude:
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: claude-${{ github.event.pull_request.number }}
cancel-in-progress: true

The biggest saving is not running at all. Gate the review job behind a cheap shell check so a two-line typo fix never pays for a model call:

name: Smart Claude Trigger
on:
pull_request:
paths:
- '**.ts'
- '**.tsx'
- '**.js'
- '**.jsx'
jobs:
analyze-complexity:
runs-on: ubuntu-latest
outputs:
should-run-claude: ${{ steps.check.outputs.result }}
steps:
- uses: actions/checkout@v4
- id: check
run: |
# Only run Claude for substantial changes
LINES_CHANGED=$(git diff --numstat origin/main..HEAD | awk '{sum+=$1+$2} END {print sum}')
if [ $LINES_CHANGED -gt 50 ]; then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
claude-review:
needs: analyze-complexity
if: needs.analyze-complexity.outputs.should-run-claude == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
prompt: '/review'
claude_args: '--max-turns 5'

For analysis passes that do not change between runs, cache the result keyed on the source files and skip the call entirely when the cache hits:

- name: Cache Claude Analysis
uses: actions/cache@v4
with:
path: .claude-cache
key: claude-${{ hashFiles('**/*.ts', '**/*.tsx') }}
- name: Run Claude Analysis
run: |
if [ -f .claude-cache/analysis.json ]; then
echo "Using cached analysis"
else
claude -p "Analyze codebase for potential improvements" \
--output-format json > .claude-cache/analysis.json
fi

Claude does not respond to @claude. Check that the Claude GitHub app is installed and has the right permissions (Contents, Issues, Pull requests — all Read & Write), that the workflow’s trigger conditions actually match the event, and that the secret name in the workflow matches the one in the repository.

CI does not run on Claude’s commits. By default, GitHub Actions do not trigger on commits made by GitHub Apps. If you need CI to run on Claude’s commits, use a custom GitHub App with actions/create-github-app-token.

Authentication errors with Bedrock or Vertex. 401 and 403 responses usually mean the OIDC token exchange is misconfigured. Verify the trust policy in the cloud account matches your repository, and that the IAM role or service account carries model-invocation permission.

Jobs get cancelled on timeout. Raise the job-level timeout-minutes: — there is no timeout_minutes action input — and lower --max-turns so a runaway job ends sooner. If both are already sane, the task is too big: split it into smaller steps with more specific prompts.

Actions cost more than expected. Long-running tasks with Opus consume significant tokens. Start with --model claude-sonnet-5 and --max-turns 5, add the substantial-diff gate above, and increase only if review quality is insufficient.

A silently ignored input. Setting model:, max_turns:, or prompt_file: on the action does nothing at all — no warning, no error, just the default behavior. When a workflow behaves as if your configuration is not there, check it against the seven inputs the v1 action actually accepts.