Practical reference for integrating Claude Code into GitHub Actions workflows. Setup, configuration, use cases, cost management, and troubleshooting.
Setup & Basic Configuration
Installing the Action
The official action is available in GitHub Marketplace:
# .github/workflows/claude-code-review.yml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Claude Code Review
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-opus
max-turns: 20
Secrets Setup
API keys must be stored in GitHub Secrets:
# 1. Add secrets in repository
# Settings → Secrets and variables → Actions → New repository secret
# Name: ANTHROPIC_API_KEY
# Value: sk-ant-v1-...
In GitHub UI:
- Open repository
- Settings → Security → Secrets and variables → Actions
- Click "New repository secret"
- Name:
ANTHROPIC_API_KEY - Value: API key from https://console.anthropic.com/account/keys
CLAUDE.md in CI Context
---
name: Code Review Agent
description: >
Specialized in security, performance, and maintainability.
Trigger: code-review, security-check, performance-audit
version: 1.0.0
---
# Code Review for CI/CD
## Context
You work in GitHub Actions with limited context and 300-second timeout.
## Rules
- Focus on: Security, Performance, Best Practices
- Max 5 critical issues per review
- Use standard report format (GH comments)
- No auto-approve — feedback only
Use Cases
1. Automatic Code Review on Pull Requests
name: Auto Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Claude Code Review
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-opus
max-turns: 15
custom-instructions: |
Review this PR for:
1. Security vulnerabilities
2. Performance issues
3. Code style violations
4. Missing tests
5. API breaking changes
output-format: github-comments
- name: Post Review as Comment
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = fs.readFileSync('claude-review.md', 'utf-8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: review
});
2. Automatic PR Descriptions
name: Generate PR Description
on:
pull_request:
types: [opened]
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Analyze Changes
id: changes
run: |
git diff origin/main..HEAD --stat > /tmp/changes.txt
echo "changes=$(cat /tmp/changes.txt)" >> $GITHUB_OUTPUT
- name: Generate Description
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet
max-turns: 5
custom-instructions: |
Create a meaningful PR description based on:
- Changed files
- Git diff
- Branch name: ${{ github.head_ref }}
Format:
## Changes
(Brief summary)
## Testing
(How it was tested)
## Breaking Changes
(If any)
output-format: raw
- name: Update PR Description
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const description = fs.readFileSync('claude-output.md', 'utf-8');
github.rest.pulls.update({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: description
});
3. Issue Triage & Auto-Labeling
name: Issue Triage
on:
issues:
types: [opened, edited]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: Analyze Issue
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet
max-turns: 3
custom-instructions: |
Analyze this GitHub issue and return labels:
- bug / feature / enhancement / documentation
- priority: critical / high / medium / low
- complexity: simple / moderate / complex
Issue Title: ${{ github.event.issue.title }}
Issue Body: ${{ github.event.issue.body }}
Output Format (JSON):
{
"labels": ["label1", "label2"],
"priority": "high",
"suggest_assignee": true
}
output-format: json
- name: Apply Labels
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const labels = JSON.parse(fs.readFileSync('claude-output.json', 'utf-8'));
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: labels.labels
});
4. Test Generation from Code
name: Generate Missing Tests
on:
pull_request:
paths:
- 'src/**/*.ts'
- 'src/**/*.js'
jobs:
generate-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Find Untested Functions
id: untested
run: |
# Find functions without tests
git diff origin/main..HEAD --name-only --diff-filter=AM | grep -E '\.(ts|js)$' > /tmp/new-files.txt
echo "files=$(cat /tmp/new-files.txt)" >> $GITHUB_OUTPUT
- name: Generate Tests
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-opus
max-turns: 10
custom-instructions: |
Create Vitest tests for these functions:
${{ steps.untested.outputs.files }}
Requirements:
- Minimum 80% code coverage
- Test edge cases
- Use mocking where needed
- Format: TypeScript/Vitest
output-format: file
output-path: test-suggestions.ts
- name: Create Test Suggestion Comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const tests = fs.readFileSync('test-suggestions.ts', 'utf-8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## Suggested Tests\n\n```typescript\n' + tests + '\n```'
});
5. Security Scanning & Vulnerability Detection
name: Security Scan
on:
pull_request:
push:
branches: [main, develop]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Scan for Security Issues
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-opus
max-turns: 20
custom-instructions: |
Check code for security issues:
1. SQL injection vulnerabilities
2. XSS/HTML injection
3. Authentication bypass
4. Exposed API keys/secrets
5. Deserialization vulnerabilities
6. CSRF issues
7. Weak cryptography
8. Path traversal bugs
Format: Markdown with severity (CRITICAL, HIGH, MEDIUM, LOW)
output-format: github-comments
- name: Fail on Critical Issues
if: failure()
run: |
echo "::error::Critical security issues found!"
exit 1
6. Changelog & Release Notes Generator
name: Generate Changelog
on:
push:
tags:
- 'v*'
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get Changes
id: changes
run: |
git log $(git describe --tags --abbrev=0)..HEAD --oneline > /tmp/commits.txt
echo "commits=$(cat /tmp/commits.txt)" >> $GITHUB_OUTPUT
- name: Generate Changelog
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet
max-turns: 5
custom-instructions: |
Create changelog/release notes from commits:
${{ steps.changes.outputs.commits }}
Format (Markdown):
## Features
- Item 1
- Item 2
## Bug Fixes
- Fix 1
## Breaking Changes
- If any
## Upgrade Guide
- Step 1
- Step 2
output-format: raw
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body_path: claude-output.md
draft: false
prerelease: false
Configuration Options
Action Input Parameters
with:
# Required
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
# Optional - Default: claude-opus
model: claude-opus | claude-sonnet | claude-haiku
# Optional - Default: 20
max-turns: 10
# Optional - Additional instructions
custom-instructions: |
Do X, Y, Z
# Optional - Default: raw
output-format: raw | json | github-comments | file
# Optional - For file output
output-path: ./results.md
# Optional - Timeout in seconds (default: 600)
timeout: 300
# Optional - Temperature (0.0 - 1.0)
temperature: 0.7
Model Selection by Task
| Task | Recommendation | Reason |
|---|---|---|
| Code Review | opus | Complex analysis |
| Test Generation | opus | Comprehensive coverage |
| PR Description | sonnet | Fast + sufficient |
| Issue Triage | haiku | Simple categorization |
| Security Scan | opus | Thorough analysis |
| Simple Linting | haiku | Fast + cost-effective |
Cost Management
Cost Optimization Strategy
name: Cost-Optimized Workflow
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Stage 1: Quick check with Haiku (cheap)
- name: Quick Lint Check
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-haiku
max-turns: 3
timeout: 60
# Stage 2: Full review with Sonnet only if flagged
- name: Full Code Review
if: contains(github.event.pull_request.labels.*.name, 'needs-full-review')
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-opus
max-turns: 15
timeout: 300
# Stage 3: Security scan only on main/protected branches
- name: Security Scan
if: github.ref == 'refs/heads/main'
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-opus
max-turns: 20
Token Budgeting
# Python script for budget tracking
import json
import os
from datetime import datetime
def track_token_usage(run_id, model, tokens_used):
"""Track Claude API token usage per workflow run"""
budget_file = "/tmp/claude-budget.json"
# Model pricing (as of March 2026)
pricing = {
"claude-opus": {"input": 0.015, "output": 0.075},
"claude-sonnet": {"input": 0.003, "output": 0.015},
"claude-haiku": {"input": 0.00080, "output": 0.004}
}
entry = {
"timestamp": datetime.now().isoformat(),
"run_id": run_id,
"model": model,
"tokens_used": tokens_used,
"cost_usd": (tokens_used * pricing[model]["input"]) / 1000
}
# Append to log
if os.path.exists(budget_file):
with open(budget_file, 'r') as f:
budget = json.load(f)
else:
budget = []
budget.append(entry)
with open(budget_file, 'w') as f:
json.dump(budget, f)
# Weekly summary
weekly_cost = sum(e["cost_usd"] for e in budget
if (datetime.now() - datetime.fromisoformat(e["timestamp"])).days < 7)
print(f"Weekly cost: ${weekly_cost:.2f}")
Error Handling
Common Issues & Fixes
1. API Key Not Found
# Problem: secret not configured
# Solution: Check GitHub secrets
- name: Debug Secret
run: |
if [ -z "${{ secrets.ANTHROPIC_API_KEY }}" ]; then
echo "ERROR: ANTHROPIC_API_KEY not set"
exit 1
fi
echo "API Key configured"
2. Timeout
# Problem: Workflow exceeds time limit
# Solution: Reduce max-turns or increase timeout
- name: Claude Code Action
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
max-turns: 5 # Reduce from 20
timeout: 120 # Increase from 60
3. Context Size Exceeded
# Problem: PR too large for single call
# Solution: Filter to specific files
- name: Review Only Changed Files
uses: anthropics/claude-code-action@v1
with:
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
custom-instructions: |
Review only these files:
- ${{ steps.files.outputs.changed }}
Best Practices
1. Caching for frequent analysis
- name: Cache Analysis Results
uses: actions/cache@v3
with:
path: ./analysis-cache
key: claude-analysis-${{ hashFiles('src/**') }}
2. Parallel reviews for large PRs
strategy:
matrix:
chunk: [1, 2, 3]
steps:
- name: Review Chunk ${{ matrix.chunk }}
uses: anthropics/claude-code-action@v1
3. Notifications on critical issues
- name: Notify on Critical Issues
if: contains(env.REVIEW_RESULT, 'CRITICAL')
uses: actions/github-script@v7
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['security-issue', 'needs-attention']
});
