Patterns for integrating Claude Code into CI/CD, scripts, and automation.
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get PR diff
id: diff
run: |
gh pr diff ${{ github.event.pull_request.number }} > diff.txt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run Claude review
run: |
result=$(cat diff.txt | claude -p "Review this PR diff for:
- Security vulnerabilities
- Performance issues
- Code quality
Output as markdown." \
--output-format json \
--allowedTools "Read,Grep")
echo "$result" | jq -r '.result' > review.md
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Post review comment
run: |
gh pr comment ${{ github.event.pull_request.number }} \
--body-file review.md
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
claude-review:
stage: review
script:
- git diff origin/main...HEAD > diff.txt
- |
cat diff.txt | claude -p "Security review" \
--output-format json \
--allowedTools "Read" \
> review.json
- cat review.json | jq -r '.result' > review.md
artifacts:
paths:
- review.md
only:
- merge_requests
pipeline {
agent any
environment {
ANTHROPIC_API_KEY = credentials('anthropic-api-key')
}
stages {
stage('Claude Analysis') {
steps {
script {
def result = sh(
script: '''
claude -p "Analyze build issues" \
--output-format json \
--allowedTools "Read,Bash"
''',
returnStdout: true
)
def json = readJSON text: result
if (json.is_error) {
error "Claude analysis failed: ${json.result}"
}
}
}
}
}
}
#!/bin/bash
set -euo pipefail
audit_pr() {
local pr_number="$1"
# Get PR diff
diff=$(gh pr diff "$pr_number")
# Run Claude analysis
result=$(echo "$diff" | claude -p \
--append-system-prompt "Security review. Output JSON: {severity, findings, recommendations}" \
--output-format json \
--allowedTools "Read,Grep,WebSearch")
# Check for errors
if [[ $(echo "$result" | jq -r '.is_error') == "true" ]]; then
echo "Error: $(echo "$result" | jq -r '.result')" >&2
return 1
fi
echo "$result" | jq -r '.result'
}
# Usage
audit_pr 123
#!/bin/bash
set -euo pipefail
process_files() {
local pattern="$1"
local prompt="$2"
find . -name "$pattern" -print0 | while IFS= read -r -d '' file; do
echo "Processing: $file"
result=$(cat "$file" | claude -p "$prompt" \
--output-format json \
--allowedTools "Read")
if [[ $(echo "$result" | jq -r '.is_error') == "false" ]]; then
echo "$result" | jq -r '.result' > "${file}.analysis.md"
fi
done
}
# Usage
process_files "*.py" "Analyze this Python file for issues"
#!/bin/bash
set -euo pipefail
run_workflow() {
# Step 1: Initial analysis
result=$(claude -p "Analyze the codebase structure" \
--output-format json \
--allowedTools "Read,Glob,Grep")
session=$(echo "$result" | jq -r '.session_id')
echo "Session: $session"
# Step 2: Deep dive with context
result=$(claude --resume "$session" \
"Now examine the authentication module in detail" \
--output-format json)
# Step 3: Generate report
claude --resume "$session" \
"Generate a security report in markdown" \
--output-format json | jq -r '.result' > report.md
echo "Report saved to report.md"
}
run_workflow
#!/bin/bash
# .git/hooks/pre-commit
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$' || true)
if [[ -n "$staged_files" ]]; then
echo "Running Claude review on staged Python files..."
for file in $staged_files; do
result=$(cat "$file" | claude -p \
"Quick code review. Report only critical issues. Be concise." \
--output-format json \
--allowedTools "Read" 2>/dev/null)
if [[ $(echo "$result" | jq -r '.is_error') == "false" ]]; then
review=$(echo "$result" | jq -r '.result')
if [[ "$review" != *"no issues"* ]] && [[ "$review" != *"looks good"* ]]; then
echo "Review for $file:"
echo "$review"
echo ""
fi
fi
done
fi
#!/bin/bash
# Run via cron: 0 8 * * * /path/to/daily-report.sh
REPORT_DIR="/var/reports/claude"
DATE=$(date +%Y-%m-%d)
mkdir -p "$REPORT_DIR"
cd /path/to/project
result=$(claude -p "Generate a daily code quality report covering:
1. Recent changes summary
2. Potential issues
3. Recommendations
Use git log for recent changes." \
--output-format json \
--allowedTools "Bash,Read,Grep")
echo "$result" | jq -r '.result' > "$REPORT_DIR/report-$DATE.md"
# Email or Slack notification
# curl -X POST "$SLACK_WEBHOOK" -d "{\"text\": \"Daily report ready\"}"
const express = require('express');
const { spawn } = require('child_process');
const app = express();
app.use(express.json());
app.post('/api/claude', async (req, res) => {
const { prompt, tools } = req.body;
const args = ['-p', prompt, '--output-format', 'json'];
if (tools) {
args.push('--allowedTools', tools.join(','));
}
const claude = spawn('claude', args);
let output = '';
claude.stdout.on('data', (data) => {
output += data.toString();
});
claude.on('close', (code) => {
try {
const result = JSON.parse(output);
res.json(result);
} catch (e) {
res.status(500).json({ error: 'Failed to parse response' });
}
});
});
app.listen(3000);
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import subprocess
import json
app = FastAPI()
class ClaudeRequest(BaseModel):
prompt: str
tools: list[str] | None = None
@app.post("/api/claude")
async def run_claude(request: ClaudeRequest):
args = ["claude", "-p", request.prompt, "--output-format", "json"]
if request.tools:
args.extend(["--allowedTools", ",".join(request.tools)])
proc = subprocess.run(args, capture_output=True, text=True)
try:
result = json.loads(proc.stdout)
return result
except json.JSONDecodeError:
raise HTTPException(status_code=500, detail="Failed to parse response")
#!/bin/bash
run_with_retry() {
local max_attempts=3
local attempt=1
local delay=5
while [[ $attempt -le $max_attempts ]]; do
result=$(claude -p "$1" --output-format json 2>&1)
if [[ $(echo "$result" | jq -r '.is_error // true') == "false" ]]; then
echo "$result"
return 0
fi
echo "Attempt $attempt failed, retrying in ${delay}s..." >&2
sleep $delay
attempt=$((attempt + 1))
delay=$((delay * 2))
done
echo "All attempts failed" >&2
return 1
}
#!/bin/bash
analyze_with_fallback() {
# Try Claude first
result=$(claude -p "$1" --output-format json 2>/dev/null)
if [[ -z "$result" ]] || [[ $(echo "$result" | jq -r '.is_error') == "true" ]]; then
echo "Claude unavailable, using fallback analysis" >&2
# Fallback to simpler analysis
run_basic_linter "$2"
return
fi
echo "$result" | jq -r '.result'
}