Skip to content

CI/CD Integration & Automation

Your team merges 30 PRs a week and every one waits hours for a human first-pass review. Flaky tests get rubber-stamped, a security regression slips through, and the nightly docs drift further from the code. You don’t need more reviewers - you need an automated first pass that runs on every PR and a way to drive Claude Code from inside your pipeline.

This is where Claude Code stops being an interactive tool and becomes part of your CI/CD. With the anthropics/claude-code-action@v1 GitHub Action and headless mode (claude -p), you can wire AI into the events you already react to: comments, labels, failed builds, and cron schedules.

  • A working @claude workflow that responds to PR and issue comments
  • An AI PR-review job that runs on every pull request using the built-in /review command
  • An auto-fix-CI job that reads failure logs and pushes a fix
  • A nightly documentation updater that opens a PR with the changes
  • A cost-gating trigger that only invokes Claude on substantial diffs
  • Copy-paste prompts for the highest-value CI automations

The fastest way to enable GitHub integration:

  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
    • API key is configured automatically
  3. Test the integration. Create an issue comment that mentions Claude:
    @claude implement this feature based on the issue description

For custom configurations or cloud providers:

.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'

Transform issues directly into pull requests:

.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'

Enhance PR reviews with AI analysis:

.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'

The built-in /review command handles the common cases. When you want a security-focused pass with a fixed output format, inline your own instructions in the prompt field instead (there is no prompt_file input):

Automatically attempt to fix CI failures:

.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".

Run Claude Code programmatically:

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"

Handle large-scale migrations:

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

Integrate with existing tools:

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
.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

Orchestrate changes across microservices:

.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.
.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
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const report = require('fs').readFileSync('security-report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});

Only run Claude on relevant changes:

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'

Reduce redundant API calls:

- 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
- name: Report Usage Metrics
if: always()
run: |
claude -p "Summarize the work done in this CI run" --output-format json > usage.json
# Send to monitoring service
curl -X POST https://metrics.company.com/claude-usage \
-H "Content-Type: application/json" \
-d @usage.json
claude-metrics.js
const { execSync } = require('child_process');
function trackClaudePerformance(command, context) {
const start = Date.now();
try {
const result = execSync(`claude -p "${command}" --output-format json`, {
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024,
});
const duration = Date.now() - start;
const parsed = JSON.parse(result);
// Send to monitoring
sendMetrics({
command,
context,
duration,
tokensUsed: parsed.usage?.total_tokens,
success: true,
});
return parsed;
} catch (error) {
sendMetrics({
command,
context,
duration: Date.now() - start,
success: false,
error: error.message,
});
throw error;
}
}

Use CLAUDE.md Files

Configure project-specific guidelines for consistent CI behavior

Cap Turns and Time

Set --max-turns in claude_args and a job-level timeout-minutes: to prevent runaway costs

Review Before Merge

Always have human review for Claude-generated changes

Monitor Costs

Track API usage and optimize triggers

Symptoms: @claude mentions ignored

Solutions:

  1. Verify GitHub App installation
  2. Check workflow permissions
  3. Ensure trigger conditions match
  4. Verify API key is set correctly
  5. Check GitHub Actions logs

For custom integrations beyond GitHub Actions:

claude-automation.ts
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs/promises';
const execAsync = promisify(exec);
async function automateCodeReview(prNumber: number) {
// Prepare the review prompt
const prompt = `Review pull request #${prNumber} for:
- Security vulnerabilities
- Performance issues
- Code style violations
- Missing tests
Use the CLAUDE.md file for project context and
.github/review-guide.md for review guidelines.`;
// Save prompt to a file for complex multi-line prompts
await fs.writeFile('/tmp/review-prompt.txt', prompt);
try {
// With `cat file | claude -p`, stdin becomes the prompt, so no positional
// argument is needed. This mirrors the official idiom:
// gh pr diff "$1" | claude -p --append-system-prompt "..." --output-format json
const { stdout } = await execAsync(
`cat /tmp/review-prompt.txt | claude -p --output-format json`,
{
cwd: process.cwd(),
env: { ...process.env }
}
);
return JSON.parse(stdout);
} catch (error) {
console.error('Claude Code review failed:', error);
throw error;
}
}
// Alternative: Stream output for real-time processing
import { spawn } from 'child_process';
function streamCodeReview(prNumber: number): Promise<string[]> {
return new Promise((resolve, reject) => {
const messages: string[] = [];
const claude = spawn('claude', [
'-p',
`Review PR #${prNumber} for security and performance`,
'--output-format', 'json'
]);
claude.stdout.on('data', (data) => {
messages.push(data.toString());
});
claude.on('close', (code) => {
if (code === 0) {
resolve(messages);
} else {
reject(new Error(`Claude Code exited with code ${code}`));
}
});
});
}

Team Workflows

Scale CI/CD patterns across your organization

Cost Management

Optimize CI/CD costs and usage

Security Patterns

Implement secure CI/CD practices