なぜ CI/CD に Claude API を統合するのか
コードのプッシュからデプロイまでの自動化が当たり前になった現代の開発現場において、AI が「コードを書く」だけでなく「コードの品質を保証する」役割を担い始めています。
GitHub Actions と Claude API を組み合わせることで、これまで人手に依存していた以下のプロセスを自動化できます。
- コードレビュー: PR 作成時に差分を Claude が解析し、バグ・セキュリティリスク・コードスメルを指摘
- テスト自動生成: 新しい関数やモジュールに対してユニットテストを自動生成
- ドキュメント更新: コード変更に追随して README・API ドキュメントを自動更新
- PR サマリー: 変更内容を自然言語で要約し、レビュワーの負担を軽減
- リリースノート生成: マージされたコミットをまとめてリリースノートを自動生成
ここではこれらすべてを本番環境で安定稼働させるための設計パターンと実装コードを体系的に解説します。読み終えれば、翌日から自分のリポジトリに導入できる状態になるでしょう。
前提知識・環境準備
必要な知識
必要な環境
- GitHub リポジトリ(無料プランでも可)
- Anthropic API キー(API キー取得手順 を参照)
- GitHub Secrets への API キー登録
GitHub Secrets への登録手順
まず、Anthropic の API キーを GitHub Secrets に安全に登録します。
1. リポジトリ → Settings → Secrets and variables → Actions
2. "New repository secret" をクリック
3. Name: ANTHROPIC_API_KEY
4. Secret: your_actual_api_key_here(※本番では実際のキーを入力)
5. "Add secret" をクリック
⚠️ 重要: API キーをワークフロー YAML に直接書かないこと。必ず ${{ secrets.ANTHROPIC_API_KEY }} で参照します。
アーキテクチャ設計:4 つの原則
本番 CI/CD パイプラインに Claude API を組み込む際、以下の 4 原則を守ることで安定・低コストな運用が実現します。
原則 1: 差分のみを Claude に渡す
PR の変更差分(git diff)だけを Claude に送ることで、トークン消費を最小化します。リポジトリ全体を送ると費用が爆発します。
原則 2: キャッシュを積極活用する
変更のないファイルの解析は、Anthropic の Prompt Caching を活用してコストを削減します。共通のシステムプロンプトをキャッシュするだけで 70-90% のコスト削減が可能です。
原則 3: 非同期・並列処理で速度を稼ぐ
ファイルごとの解析を並列実行することで、大きな PR でも許容範囲内の待ち時間に抑えられます。
原則 4: 失敗をグレースフルに扱う
Claude API の呼び出しが失敗してもビルドを止めない設計にします。AI レビューはオプション機能であり、コアのビルド・テストとは疎結合にします。
ユースケース 1:自動コードレビュー
ワークフロー定義(YAML)
# .github/workflows/ai-code-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
# 差分が大きすぎるPRはスキップ(コスト対策)
paths-ignore:
- '*.lock'
- '*.json'
- 'dist/**'
jobs:
ai-review:
runs-on: ubuntu-latest
# 同じPRで重複実行を防ぐ(コスト対策)
concurrency:
group: ai-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
pull-requests: write # PRにコメントするため
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # 差分取得に必要
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: pip install anthropic PyGithub
- name: Run AI Code Review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: python .github/scripts/ai_code_review.py
# Claudeの呼び出しが失敗してもビルドを止めない
continue-on-error: true
Python スクリプト(本体)
# .github/scripts/ai_code_review.py
import os
import subprocess
import anthropic
from github import Github
# 環境変数から設定を取得
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
REPO_NAME = os.environ["GITHUB_REPOSITORY"]
PR_NUMBER = int(os.environ["PR_NUMBER"])
BASE_SHA = os.environ["BASE_SHA"]
HEAD_SHA = os.environ["HEAD_SHA"]
# 差分の最大サイズ(トークン節約のため)
MAX_DIFF_CHARS = 20000
def get_diff() -> str:
"""git diff でPRの変更差分を取得"""
result = subprocess.run(
["git", "diff", f"{BASE_SHA}...{HEAD_SHA}",
"--", "*.py", "*.ts", "*.tsx", "*.js", "*.jsx",
"*.go", "*.rs", "*.java", "*.swift"],
capture_output=True, text=True
)
diff = result.stdout
if len(diff) > MAX_DIFF_CHARS:
# 差分が大きすぎる場合は先頭だけ送る
diff = diff[:MAX_DIFF_CHARS] + "\n\n[差分が大きすぎるため省略されました]"
return diff
def review_with_claude(diff: str) -> str:
"""Claude APIでコードレビューを実行"""
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
# システムプロンプトはキャッシュ対象(コスト最適化)
system_prompt = """あなたはシニアエンジニアとしてコードレビューを行います。
以下の観点でレビューしてください:
1. **バグ・ロジックエラー**: null参照、境界値、競合状態など
2. **セキュリティリスク**: インジェクション、認証不備、シークレットの露出など
3. **パフォーマンス**: 不要なループ、N+1クエリ、メモリリークなど
4. **コードの可読性**: 変数名、関数の長さ、複雑度など
5. **ベストプラクティス**: エラーハンドリング、テスタビリティなど
重大な問題がない場合は「問題なし」と明記してください。
コメントはMarkdownで記述し、簡潔に絞ってください(5つ以内)。"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2000,
system=[
{
"type": "text",
"text": system_prompt,
# キャッシュを有効化(同じシステムプロンプトの再利用でコスト削減)
"cache_control": {"type": "ephemeral"}
}
],
messages=[
{
"role": "user",
"content": f"以下のコード差分をレビューしてください:\n\n```diff\n{diff}\n```"
}
]
)
return response.content[0].text
def post_comment(review: str):
"""GitHubのPRにコメントを投稿"""
gh = Github(GITHUB_TOKEN)
repo = gh.get_repo(REPO_NAME)
pr = repo.get_pull(PR_NUMBER)
# 既存のAIレビューコメントを削除(重複防止)
for comment in pr.get_issue_comments():
if comment.body.startswith("## 🤖 AI コードレビュー"):
comment.delete()
pr.create_issue_comment(f"## 🤖 AI コードレビュー\n\n{review}\n\n---\n*このレビューは Claude API によって自動生成されました。*")
def main():
diff = get_diff()
if not diff.strip():
print("差分なし。レビューをスキップします。")
return
print(f"差分サイズ: {len(diff)} 文字")
review = review_with_claude(diff)
post_comment(review)
print("✅ レビューコメントを投稿しました")
if __name__ == "__main__":
main()
このスクリプトにより、PR を開くたびに Claude がコードを解析し、バグ・セキュリティリスク・改善点を自動でコメントします。
ユースケース 2:ユニットテスト自動生成
新しいファイルや関数が追加されたとき、Claude がテストコードを自動生成するワークフローです。
ワークフロー定義
# .github/workflows/ai-test-generation.yml
name: AI Test Generation
on:
pull_request:
types: [opened, synchronize]
paths:
- 'src/**/*.py' # Pythonファイルが変更されたときだけ実行
- 'src/**/*.ts'
jobs:
generate-tests:
runs-on: ubuntu-latest
permissions:
contents: write # テストファイルをコミットするため
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.head_ref }}
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install anthropic
- name: Generate Tests
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: python .github/scripts/generate_tests.py
continue-on-error: true
- name: Commit generated tests
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add tests/
git diff --staged --quiet || git commit -m "test: AI が自動生成したユニットテストを追加 [skip ci]"
git push
テスト生成スクリプト
# .github/scripts/generate_tests.py
import os
import subprocess
import anthropic
from pathlib import Path
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
BASE_SHA = os.environ.get("BASE_SHA", "HEAD~1")
def get_new_python_files() -> list[str]:
"""新規追加されたPythonファイルを取得"""
result = subprocess.run(
["git", "diff", "--name-only", "--diff-filter=A", f"{BASE_SHA}...HEAD"],
capture_output=True, text=True
)
return [f for f in result.stdout.strip().split("\n")
if f.endswith(".py") and f.startswith("src/") and f]
def generate_test(source_file: str) -> str | None:
"""Claudeにテストコードを生成させる"""
source_code = Path(source_file).read_text()
if len(source_code) > 10000:
return None # 大きすぎるファイルはスキップ
response = client.messages.create(
model="claude-haiku-4-5-20251001", # 高速・低コストモデルを使用
max_tokens=3000,
system="""あなたはPythonのテスト専門家です。
提供されたソースコードに対してpytestのユニットテストを生成してください。
- 各関数の正常系・異常系をカバー
- モックが必要な場合は unittest.mock を使用
- テストは独立して実行可能にする
- docstringで各テストの意図を説明
コードのみを出力し、説明は不要です。""",
messages=[
{"role": "user", "content": f"以下のコードのテストを生成してください:\n\n```python\n{source_code}\n```"}
]
)
test_code = response.content[0].text
# マークダウンのコードブロックを除去
if "```python" in test_code:
test_code = test_code.split("```python")[1].split("```")[0].strip()
return test_code
def main():
new_files = get_new_python_files()
if not new_files:
print("新規ファイルなし。スキップします。")
return
Path("tests").mkdir(exist_ok=True)
for source_file in new_files:
print(f"テスト生成中: {source_file}")
test_code = generate_test(source_file)
if not test_code:
continue
# src/module/utils.py → tests/test_module_utils.py
test_name = "test_" + source_file.replace("src/", "").replace("/", "_")
test_path = Path("tests") / test_name
test_path.write_text(test_code)
print(f"✅ 生成完了: {test_path}")
if __name__ == "__main__":
main()
ポイント: テスト生成には高速・低コストな claude-haiku-4-5-20251001 を使用します。コードレビューのような高精度が必要なタスクには claude-sonnet-4-6 を、シンプルな自動生成には Haiku を使い分けるのが本番での鉄則です。
ユースケース 3:PR サマリー自動生成
PR の変更内容を自然言語で要約し、レビュワーの理解を助けるコメントを自動投稿します。
# .github/workflows/ai-pr-summary.yml
name: AI PR Summary
on:
pull_request:
types: [opened] # PR作成時のみ(同期時は不要)
jobs:
summarize:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install anthropic PyGithub
- name: Generate PR Summary
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
python - <<'EOF'
import os, subprocess, anthropic
from github import Github
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
gh = Github(os.environ["GITHUB_TOKEN"])
repo = gh.get_repo(os.environ["GITHUB_REPOSITORY"])
pr = repo.get_pull(int(os.environ["PR_NUMBER"]))
# コミットメッセージを収集
commits = [c.commit.message for c in pr.get_commits()]
diff = subprocess.run(
["git", "diff", "--stat", f"{os.environ['BASE_SHA']}...{os.environ['HEAD_SHA']}"],
capture_output=True, text=True
).stdout[:3000]
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1500,
messages=[{
"role": "user",
"content": f"""以下のPR情報から、レビュワー向けの日本語サマリーを作成してください。
**PR タイトル**: {os.environ['PR_TITLE']}
**コミット一覧**:
{chr(10).join(f'- {m}' for m in commits[:10])}
**変更ファイル統計**:
{diff}
サマリーには以下を含めてください:
1. 変更の目的(1〜2文)
2. 主な変更内容(箇条書き3〜5点)
3. テスト方法の提案
4. レビュワーが特に注意すべき点"""
}]
)
summary = response.content[0].text
pr.create_issue_comment(f"## 📋 AI PR サマリー\n\n{summary}\n\n---\n*Claude API が自動生成しました*")
print("✅ PRサマリーを投稿しました")
EOF
continue-on-error: true
ユースケース 4:リリースノート自動生成
タグをプッシュしたときに、前回のタグからの変更を Claude がまとめてリリースノートを自動生成します。
# .github/workflows/ai-release-notes.yml
name: AI Release Notes
on:
push:
tags:
- 'v*'
jobs:
release-notes:
runs-on: ubuntu-latest
permissions:
contents: write # GitHub Releasesを作成するため
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install anthropic PyGithub
- name: Generate Release Notes
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ github.ref_name }}
run: |
python - <<'EOF'
import os, subprocess, anthropic
from github import Github
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
gh = Github(os.environ["GITHUB_TOKEN"])
repo = gh.get_repo(os.environ["GITHUB_REPOSITORY"])
tag = os.environ["TAG_NAME"]
# 前回のタグを取得
tags = list(repo.get_tags())
prev_tag = tags[1].name if len(tags) > 1 else ""
# コミット一覧を取得
if prev_tag:
commits_output = subprocess.run(
["git", "log", f"{prev_tag}..{tag}", "--pretty=format:%s"],
capture_output=True, text=True
).stdout
else:
commits_output = subprocess.run(
["git", "log", tag, "--pretty=format:%s", "-50"],
capture_output=True, text=True
).stdout
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2000,
messages=[{
"role": "user",
"content": f"""以下のコミット一覧からリリースノートを生成してください。
**バージョン**: {tag}
**前バージョン**: {prev_tag or '初回リリース'}
**コミット一覧**:
{commits_output[:5000]}
以下の形式でMarkdownを生成してください:
- ## 🚀 新機能(New Features)
- ## 🐛 バグ修正(Bug Fixes)
- ## ⚡ 改善(Improvements)
- ## 🔧 内部変更(Internal)
- ## ⚠️ 破壊的変更(Breaking Changes)(あれば)
関係のないカテゴリは省略してください。"""
}]
)
notes = response.content[0].text
# GitHub Releaseを作成
repo.create_git_release(
tag=tag,
name=f"Release {tag}",
message=notes,
draft=False,
prerelease="rc" in tag or "beta" in tag or "alpha" in tag
)
print(f"✅ リリース {tag} を作成しました")
EOF
本番運用のベストプラクティス
コスト管理:月額を予測・制御する
Claude API のコストは利用量に比例します。CI/CD に組み込む際は、以下のコスト制御を必ず実装してください。
- 大きなPRはスキップ:
git diff --stat で変更行数が 1,000 行超の場合はスキップ
- モデルを使い分ける: レビューに Sonnet、サマリー・テスト生成に Haiku
- プロンプトキャッシュ: システムプロンプトに
cache_control を設定(詳細は プロンプトキャッシュ活用ガイド を参照)
- concurrency設定: 同じPRで重複実行しないよう
concurrency を設定
月間コストの目安(1日20PR、Sonnet使用):
- 1PR あたり平均 3,000 トークン入力 + 1,000 トークン出力
- 月間: 約 60,000 入力トークン + 20,000 出力トークン
- 推定費用: 約 $0.5〜$1.0 /月(非常に低コスト)
セキュリティ:シークレット管理の徹底
# ❌ 絶対にやってはいけないこと
env:
API_KEY: "sk-ant-..." # ハードコードは厳禁
# ✅ 正しい方法
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
また、Claude API プロダクションセキュリティ完全ガイド で解説しているプロンプトインジェクション対策も CI/CD 環境では重要です。PR のタイトルやコミットメッセージに悪意あるプロンプトが含まれる可能性があるため、ユーザー入力を直接プロンプトに展開しないよう注意してください。
レート制限対策:リトライと指数バックオフ
import time
import anthropic
from anthropic import RateLimitError, APIStatusError
def call_claude_with_retry(client, **kwargs, max_retries=3):
"""指数バックオフ付きリトライ"""
for attempt in range(max_retries):
try:
return client.messages.create(**kwargs)
except RateLimitError as e:
if attempt == max_retries - 1:
raise
wait_time = 2 ** attempt # 1秒 → 2秒 → 4秒
print(f"レート制限に達しました。{wait_time}秒後にリトライします({attempt+1}/{max_retries})")
time.sleep(wait_time)
except APIStatusError as e:
if e.status_code >= 500:
# サーバーエラーはリトライ
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
else:
raise # クライアントエラーはリトライしない
タイムアウト設定:ビルドを長時間ブロックしない
jobs:
ai-review:
runs-on: ubuntu-latest
timeout-minutes: 5 # 最大5分でタイムアウト
steps:
- name: Run AI Code Review
timeout-minutes: 3 # ステップ単位でも設定
continue-on-error: true # タイムアウトしてもビルドは続ける
応用:モノレポへの対応
複数のサービスを含むモノレポでは、変更されたサービスに絞ってレビューを実行する設計が重要です。
def get_changed_services(base_sha: str, head_sha: str) -> list[str]:
"""変更されたサービス(ディレクトリ)を特定"""
result = subprocess.run(
["git", "diff", "--name-only", f"{base_sha}...{head_sha}"],
capture_output=True, text=True
)
changed_files = result.stdout.strip().split("\n")
# サービスディレクトリを抽出(例: services/user-service/...)
services = set()
for f in changed_files:
parts = f.split("/")
if len(parts) >= 2 and parts[0] == "services":
services.add(parts[1])
return list(services)
def review_service(service: str, diff: str) -> str:
"""サービスごとに特化したシステムプロンプトでレビュー"""
service_context = {
"user-service": "このサービスはユーザー認証を担当します。セキュリティに特に注意してください。",
"payment-service": "このサービスは決済を担当します。データの整合性とエラーハンドリングを重視してください。",
}.get(service, "")
# サービス固有のコンテキストをプロンプトに追加
return f"[{service}] {service_context}\n\n{diff}"
まとめ
要点をまとめると次の通りです。
- コードレビュー: PR 差分を Claude に渡してバグ・セキュリティ・可読性を自動指摘
- テスト自動生成: 新規ファイルに対して pytest テストを自動生成しコミット
- PR サマリー: 変更内容を自然言語で要約してレビュワーの理解を支援
- リリースノート: タグプッシュをトリガーに GitHub Release を自動作成
- コスト管理: Haiku/Sonnet の使い分け・プロンプトキャッシュ・差分制限で月 $1 以下を実現
- セキュリティ: Secrets 使用・プロンプトインジェクション対策・タイムアウト設定が必須
AI によるコードレビューはチームの生産性を底上げし、ヒューマンエラーを削減します。ただし、あくまでアシスタントとして位置づけ、最終判断は常に人間が行う設計を守る点が肝心です。
Tool Use や Streaming を活用したさらに高度な実装については、Tool Use 完全ガイド も合わせてご参照ください。