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
@claudeworkflow that responds to PR and issue comments - An AI PR-review job on every pull request, using the built-in
/reviewcommand 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
Setting up the GitHub app in one command
Section titled “Setting up the GitHub app in one command”The fastest path runs the installer from inside Claude Code.
-
Launch the REPL and run the installer
Run
claudeto 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. -
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.
-
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.
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'name: Claude via Bedrock
permissions: contents: write pull-requests: write issues: write id-token: write # For OIDC
jobs: claude-pr: if: contains(github.event.comment.body, '@claude') runs-on: ubuntu-latest env: AWS_REGION: us-west-2 # Sonnet 5 is served through the Bedrock Mantle endpoint. CLAUDE_CODE_USE_MANTLE: '1' steps: - uses: actions/checkout@v4
- name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: us-west-2
- uses: anthropics/claude-code-action@v1 with: use_bedrock: 'true' github_token: ${{ secrets.GITHUB_TOKEN }} trigger_phrase: '@claude' # Mantle uses dateless anthropic.* IDs; legacy InvokeModel uses regional profiles. claude_args: '--model anthropic.claude-sonnet-5 --max-turns 10'name: Claude via Vertex AI
permissions: contents: write pull-requests: write issues: write id-token: write # For workload identity
jobs: claude-pr: if: contains(github.event.comment.body, '@claude') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Authenticate to Google Cloud id: auth uses: google-github-actions/auth@v2 with: workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- uses: anthropics/claude-code-action@v1 with: use_vertex: 'true' github_token: ${{ secrets.GITHUB_TOKEN }} trigger_phrase: '@claude' # Sonnet 5 uses a pinned, dateless Google Cloud ID. claude_args: '--model claude-sonnet-5 --max-turns 10' env: ANTHROPIC_VERTEX_PROJECT_ID: ${{ steps.auth.outputs.project_id }} CLOUD_ML_REGION: globalWhat 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”The built-in /review pass
Section titled “The built-in /review pass”/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:
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.
Turning issues into pull requests
Section titled “Turning issues into pull requests”Label an issue and let the pipeline open the PR:
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”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:
Scheduled and event-driven maintenance
Section titled “Scheduled and event-driven maintenance”A daily summary posted to your log issue
Section titled “A daily summary posted to your log issue”name: Daily Reporton: 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-labeling new issues
Section titled “Auto-labeling new issues”Auto-fixing lint and type errors on new PRs
Section titled “Auto-fixing lint and type errors on new PRs”name: Auto-Fixon: 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"Nightly documentation updates
Section titled “Nightly documentation updates”This one runs headless rather than through the action, because it chains several passes and then hands the result to a PR-creating action:
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-docsDriving 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:
# Simple one-shot commandclaude -p "Update all copyright headers to 2026" --output-format json
# With specific permissionsclaude -p "Fix the failing test in auth.test.js" \ --allowedTools "Edit" "Read" "Bash" \ --output-format json
# Pipe data for processingcat 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.
Fan-out over many files
Section titled “Fan-out over many files”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:
#!/bin/bash# Generate task listclaude -p "List all React class components that need hooks migration" \ --output-format json > tasks.json
# Process each componentjq -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"donePiping one pass into the next
Section titled “Piping one pass into the next”# Code quality pipelinenpm 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.mdA pre-commit hook that clears TODOs
Section titled “A pre-commit hook that clears TODOs”#!/bin/bash# Check for TODO commentsif 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 -ufiCoordinating 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:
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:
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.mdKeep 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.
Keeping CI spend under control
Section titled “Keeping CI spend under control”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-turnsinclaude_argsprevents a runaway job. Five to ten turns is enough for most reviews. - Select the model explicitly. Account defaults differ, so pass
--model claude-sonnet-5in 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 notimeout_minutesaction 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: trueOnly invoking Claude on substantial diffs
Section titled “Only invoking Claude on substantial diffs”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 fiWhen GitHub Actions automation breaks
Section titled “When GitHub Actions automation breaks”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.
Where to go next with CI/CD automation
Section titled “Where to go next with CI/CD automation”- Monitoring and cost control — track CI spend alongside developer usage
- Hooks and automation — combine hooks with GitHub Actions for end-to-end automation
- Custom commands — write review commands that work in both the CLI and CI
- Team collaboration — scale these patterns across an organization