Claude Code × GitHub Actions でCI/CDを賢くする——AIレビュー・自動修正・コスト管理の実装パターン
Claude Code と GitHub Actions を連携してCI/CDパイプラインにAIを組み込む実践ガイド。PRレビュー自動化・テスト失敗の自動分析・コスト最適化まで、settings.jsonとワークフローファイルを丸ごと公開。
エンジニアのゆとです。
Claude Codeをローカルで使っている人は多いと思うけど、CI/CDに組み込んでいる人はまだ少ない印象がある。
僕は3ヶ月前から GitHub Actions のワークフローにClaude Codeを差し込んでいる。PRのレビュー自動化・テスト失敗の原因分析・Lintエラーの自動修正が主な用途で、「CIを通すためだけに何往復もする」みたいな時間がかなり減った。
この記事ではその実装を全部公開する。トークンコストの話もきちんとする。
なぜCI/CDにClaude Codeを入れるのか
ローカルのClaude Codeとの違いを先に整理しておく。
| 用途 | ローカルCC | CI上のCC |
|---|---|---|
| インタラクティブ作業 | 向いている | 向いていない |
| PR全体のレビュー | 手間がかかる | 自動化できる |
| テスト失敗の分析 | その都度手動 | 自動でissueに貼れる |
| コード修正のpush | 手動 | ブランチにpushできる |
| コスト管理 | 個人の裁量 | Secrets + Budgetで制御 |
CI上ではインタラクティブ性は不要で、「決まったタスクを確実にこなす」用途がメインになる。
そこでClaude Codeの --print モード(非対話型)と Anthropic SDK を組み合わせるアーキテクチャが現実的になる。
前提環境
- GitHub Actions(標準ランナーで動く)
- Anthropic API キー(
ANTHROPIC_API_KEYをRepositoryのSecretsに登録) - Node.js or Python(SDKを使う場合)
Claude CodeをそのままCI上で動かす方法と、Anthropic SDKをスクリプト経由で呼ぶ方法の2通りある。シンプルさで言えば後者の方が安定しやすい。
パターン1: PR差分をAIにレビューさせる
最初にやる価値が高いのはこれ。
PRが開かれたタイミングで差分を取ってClaudeに食わせ、コメントをPRに投稿する。
ワークフローファイル
.github/workflows/ai-review.yml:
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
ai-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr_diff.txt
echo "lines=$(wc -l < pr_diff.txt)" >> $GITHUB_OUTPUT
- name: Skip if diff too large
if: steps.diff.outputs.lines > 800
run: |
echo "差分が大きすぎるためAIレビューをスキップ(${{ steps.diff.outputs.lines }}行)"
exit 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install anthropic
- name: Run AI review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: python .github/scripts/ai_review.py
- name: Post review comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = fs.readFileSync('ai_review_result.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: review
});
レビュースクリプト
.github/scripts/ai_review.py:
import anthropic
import os
diff = open('pr_diff.txt').read()
client = anthropic.Anthropic()
prompt = f"""以下のPR差分をコードレビューしてください。
## レビューの観点
1. バグや論理的な誤りはないか
2. セキュリティ上の問題点(特に入力バリデーション、認証系)
3. パフォーマンスへの影響
4. 可読性・保守性の改善点
5. テストが不足していそうな箇所
## 出力形式
Markdown形式で。指摘がない場合は「特に問題なし」と書く。
重大な問題には⚠️、軽微な提案には💡、良い実装には✅ をつける。
## PR差分
""" + diff[:6000]
message = client.messages.create(
model="claude-opus-4-5",
max_tokens=2048,
messages=[
{"role": "user", "content": prompt}
],
system="あなたはシニアエンジニアのコードレビュアーです。日本語で、具体的かつ建設的なフィードバックをしてください。"
)
with open('ai_review_result.md', 'w') as f:
f.write("## 🤖 AIコードレビュー\n\n")
f.write(message.content[0].text)
f.write("\n\n---\n*このレビューはClaude (Anthropic)によって自動生成されました。最終判断は人間のレビュアーが行ってください。*")
差分が800行を超えるとスキップする条件を入れているのは、コスト爆発を防ぐため。大きなPRはそもそも分割すべきというメッセージも込めている。
パターン2: テスト失敗の原因をAIが分析してPRにコメント
テストが落ちた時の「なぜ落ちたか」をAIに分析させて、PRに自動コメントする。
name: Test with AI Analysis
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Run tests
id: tests
run: |
npm test 2>&1 | tee test_output.txt
echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
continue-on-error: true
- name: AI failure analysis
if: steps.tests.outputs.exit_code != '0'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python3 << 'EOF'
import anthropic, os
output = open('test_output.txt').read()[-4000:] # 末尾4000文字
client = anthropic.Anthropic()
msg = client.messages.create(
model="claude-haiku-4-5", # 分析だけなのでHaikuで十分
max_tokens=1024,
messages=[{"role": "user", "content": f"テスト失敗のログを分析して、原因と修正方針を簡潔に教えてください:\n\n{output}"}]
)
analysis = msg.content[0].text
with open('failure_analysis.md', 'w') as f:
f.write(f"## ⚠️ テスト失敗の分析\n\n{analysis}")
EOF
- name: Comment on PR
if: steps.tests.outputs.exit_code != '0' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
if (fs.existsSync('failure_analysis.md')) {
const analysis = fs.readFileSync('failure_analysis.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: analysis
});
}
テスト分析はHaikuを使うのがポイント。分析タスクはOpusやSonnetを使わなくてもHaikuで十分な精度が出る。コストが1/10以下になる。
パターン3: Lintエラーの自動修正をブランチにpush
これは少し攻めた設定。Lintが落ちたら自動修正してcommitする。
name: Auto Lint Fix
on:
pull_request:
types: [opened, synchronize]
jobs:
lint-fix:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.head_ref }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Run lint
id: lint
run: npx eslint . --format json > lint_result.json 2>&1 || true
- name: AI fix lint errors
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python3 << 'EOF'
import anthropic, json, os, subprocess
with open('lint_result.json') as f:
results = json.load(f)
# エラーがある最初の3ファイルだけ修正(コスト制御)
files_with_errors = [r for r in results if r['errorCount'] > 0][:3]
if not files_with_errors:
print("Lintエラーなし")
exit(0)
client = anthropic.Anthropic()
for file_result in files_with_errors:
filepath = file_result['filePath']
errors = file_result['messages']
if not os.path.exists(filepath):
continue
code = open(filepath).read()
error_desc = '\n'.join([f"L{e['line']}: {e['message']} ({e['ruleId']})" for e in errors])
prompt = "以下のファイルのLintエラーを修正してください。\n\nエラー一覧:\n" + error_desc + "\n\nファイル内容:\n" + code + "\n\n修正済みのファイル全体をコードブロックで返してください。"
msg = client.messages.create(
model="claude-haiku-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": prompt
}]
)
response = msg.content[0].text
# コードブロックを抽出
import re
match = re.search(r'```(?:\w+)?\n(.*?)```', response, re.DOTALL)
if match:
with open(filepath, 'w') as f:
f.write(match.group(1))
print(f"修正: {filepath}")
EOF
- name: Commit fixes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet; then
echo "変更なし"
else
git add -A
git commit -m "fix: AI auto-fix lint errors [skip ci]"
git push
fi
[skip ci] をコミットメッセージに入れているのは無限ループを防ぐため。自動pushがまた同じワークフローをトリガーしてしまう。
コスト管理の設計
CI上でAPIを使う場合、コストが青天井になりやすい。対策をいくつか。
月次予算アラート
Anthropic Consoleの「Budgets」でプロジェクト別に上限を設定できる(2026年4月時点でGA済み)。CIからのAPI呼び出しは専用のAPIキーを発行して、その分だけ予算を切るのが安全。
トークン使用量の記録
- name: Log token usage
run: |
# ai_review.pyの出力からusageを記録
echo "$(date): PR #${{ github.event.number }}" >> .github/ai_usage.log
実際に3ヶ月運用してわかったコスト感:
| ワークフロー | 使用モデル | 1回あたりコスト | 月間(50PR想定) |
|---|---|---|---|
| PRレビュー | claude-opus-4-5 | $0.05〜0.15 | $2.5〜7.5 |
| テスト分析 | claude-haiku-4-5 | $0.003〜0.01 | $0.15〜0.5 |
| Lint修正 | claude-haiku-4-5 | $0.01〜0.05 | $0.5〜2.5 |
月$10〜15以内で収まる。PRレビューに人間が費やす時間(1PR=10分として500分/月)との費用対効果を考えると十分に安い。
実行頻度の制御
# 小さなコミットには走らせない
on:
pull_request:
paths:
- 'src/**'
- 'tests/**'
# docs/のみの変更はスキップ
CLAUDE.md をCI環境向けに設定する
Claude Codeをそのままrunnerで動かす場合(非SDKアプローチ)は、プロジェクトの .claude/settings.json にCI専用の設定を入れる。
{
"permissions": {
"allow": [
"Bash(git:*)",
"Bash(npm:*)",
"Bash(python:*)",
"Read",
"Write"
],
"deny": [
"Bash(curl:*)",
"Bash(wget:*)"
]
}
}
外部への通信を制限するのはセキュリティ上の基本。CI上ではネットワークアクセスが最小限になるように設計する。
実際に運用してわかった罠
罠1: 差分が大きいとLLMが混乱する500行を超える差分をそのままClaudeに渡すと、後半の変更を無視するかコメントがふんわりになる。重要な変更だけに絞るか、ファイル単位で分割して呼ぶ方が精度が上がる。
罠2:.env ファイルをContextに混入させるな
git diff で差分を取る時、誤って .env の変更が含まれるケースがある。APIにシークレットが渡るリスクがあるので、差分からシークレット系ファイルを除外するフィルタを入れること。
# 差分のフィルタリング
EXCLUDE_PATTERNS = ['.env', 'secrets', 'credentials', '.pem']
lines = [l for l in diff.split('\n')
if not any(p in l for p in EXCLUDE_PATTERNS)]
罠3: Bot同士の無限コメントループ
AIのコメントにAIが返信する、みたいな状況になる可能性がある。ワークフローに if: github.actor != 'github-actions[bot]' を入れておく。
まとめ
Claude Code × GitHub Actions の組み合わせで現実的に使えるパターンは3つ:
- PRレビュー自動化 — Diffを渡してOpusにレビューさせる。週10PR以上あるなら費用対効果が出る
- テスト失敗分析 — Haikuで十分。落ちたログを食わせるだけ
- Lint自動修正 — 攻めた設定だが、機械的なミスを減らすには有効
コストは月$10〜15。人間がPRレビューにかける時間との比較でペイするかどうかは、チームの規模と開発ペースによる。
関連記事も参考に。



