⚠️ この記事はアフィリエイト広告(プロモーション)を含みます。リンク先で発生した収益の一部が運営者に支払われますが、読者の購入価格には一切影響ありません。
結論から言うと、このコピペで「PRを開いた瞬間にClaudeがレビューコメントを返すbot」が動く
この記事を読み終えると、pull_request をトリガーに Claude Code SDK を GitHub Actions 上で実行し、差分だけをレビューさせて 行コメント付きで返す bot が自分のリポジトリで動くようになる。私が実運用しているワークフローをほぼそのまま載せる。
先に実測値を出す。社内の TypeScript モノレポ(PR 約 140 本/月)に導入した結果、Anthropic の API 課金は 月 3,200 円前後(Claude Haiku 4.5 を一次レビュー、Sonnet をエスカレーション時のみ)。最初の素朴な実装は「全ファイルを丸投げ」していて、どうでもいいスタイル指摘を量産し 誤検知(人間がResolveで即閉じたコメント)が 71% あった。後述する3つの変更で 誤検知 42% 減(71%→41%) まで落とした。その差分も全部書く。
GitHub Actions で Claude Code SDK を呼ぶ最小ワークフロー
まず動く最小構成。anthropics/claude-code-action を使わず、あえて SDK を直叩き する。理由は後半で書くが、コメントを「差分の行に紐付ける」制御を自分で握りたいからだ。
.github/workflows/claude-review.yml:
name: Claude PR Review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write # 行コメント投稿に必須。read だけだと 403 で死ぬ
jobs:
review:
runs-on: ubuntu-latest
# ドラフトと bot の PR はスキップ(課金の無駄打ち防止)
if: github.event.pull_request.draft == false && github.actor != 'dependabot[bot]'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # base との差分を取るため浅くしない
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install anthropic==0.69.0
- name: Run Claude review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: python .github/scripts/review.py
ポイントは permissions の pull-requests: write。ここを read のままにして最初の30分を溶かした。Actions のデフォルト GITHUB_TOKEN は権限を絞ると行コメント API(POST /pulls/{n}/comments)で HTTP 403 を返すが、エラーメッセージが「Resource not accessible by integration」としか出ず、トークンが悪いのか権限が悪いのか分かりにくい。write を明示すれば解決する。
Python で差分だけを Claude Haiku 4.5 に渡し、行コメントで返すスクリプト
本体。git diff で base...head の unified diff だけを抜き、Claude に「指摘がある行だけ」を JSON で返させ、その JSON を GitHub の Review API に変換する。全文を載せる。
.github/scripts/review.py:
import json
import os
import subprocess
import urllib.request
from anthropic import Anthropic
client = Anthropic() # ANTHROPIC_API_KEY を env から読む
REPO = os.environ["REPO"]
PR = os.environ["PR_NUMBER"]
BASE, HEAD = os.environ["BASE_SHA"], os.environ["HEAD_SHA"]
# 1) 差分を取得。lock ファイルやスナップショットは除外して課金を削る
diff = subprocess.run(
["git", "diff", f"{BASE}...{HEAD}", "--",
".", ":(exclude)**/*.lock", ":(exclude)**/__snapshots__/**",
":(exclude)**/package-lock.json"],
capture_output=True, text=True, check=True,
).stdout
if len(diff) > 60_000: # 大型PRはトークン爆発するので頭で切る
diff = diff[:60_000] + "\n...(truncated)"
if not diff.strip():
print("no reviewable diff"); raise SystemExit(0)
SYSTEM = (
"あなたは熟練のコードレビュアー。指摘は『バグ・セキュリティ・データ破壊・"
"明確な仕様逸脱』のみ。命名やフォーマットなど好みの問題は一切返さない。"
"確信が持てない指摘は出さない。出力は指定 JSON のみ。"
)
TOOL = {
"name": "post_review",
"description": "レビュー指摘を構造化して返す",
"input_schema": {
"type": "object",
"properties": {
"comments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {"type": "string"},
"line": {"type": "integer", "description": "新ファイル側の行番号"},
"severity": {"type": "string", "enum": ["high", "medium"]},
"body": {"type": "string"},
},
"required": ["path", "line", "severity", "body"],
},
}
},
"required": ["comments"],
},
}
resp = client.messages.create(
model="claude-haiku-4-5-20251001", # 一次レビューは Haiku で十分
max_tokens=2048,
system=SYSTEM,
tools=[TOOL],
tool_choice={"type": "tool", "name": "post_review"},
messages=[{"role": "user", "content": f"以下のdiffをレビュー:\n\n{diff}"}],
)
comments = next(
b.input["comments"] for b in resp.content if b.type == "tool_use"
)
print(f"claude raised {len(comments)} comments")
def gh(method, path, payload):
req = urllib.request.Request(
f"https://api.github.com{path}",
data=json.dumps(payload).encode(),
method=method,
headers={
"Authorization": f"Bearer {os.environ['GH_TOKEN']}",
"Accept": "application/vnd.github+json",
},
)
return urllib.request.urlopen(req).read()
# 2) high/medium のみ行コメント化して 1 リクエストにまとめる
review_comments = [
{"path": c["path"], "line": c["line"], "side": "RIGHT",
"body": f"**[{c['severity']}]** {c['body']}"}
for c in comments
]
if not review_comments:
print("no actionable comment; skip posting"); raise SystemExit(0)
gh("POST", f"/repos/{REPO}/pulls/{PR}/reviews", {
"event": "COMMENT",
"body": f"Claude Haiku 4.5 による自動レビュー({len(review_comments)}件)",
"comments": review_comments,
})
print("posted")
これを置いて ANTHROPIC_API_KEY を Secrets に入れれば、PR を開いた瞬間にレビューが付く。tool_choice で post_review を強制 している点が肝で、これにより「了解しました、レビューします」みたいな前置きテキストが混ざらず、JSON パース失敗がゼロになった。最初は普通の文章で返させて正規表現で JSON を抜いていたが、20本に1本くらい Markdown のコードフェンスを付けてきて壊れた。tool 強制にしてからその事故は消えた。
誤検知を71%→41%に下げた、Claude設定の3つの実変更
ここが本題。最初の実装は「全部見て気づいたこと全部言って」だった。結果、変数名やコメント不足の指摘が洪水になり、開発者がミュートし始めた。次の3つで実用域に入った。
1) git diff の :(exclude) で lock とスナップショットを物理排除。 package-lock.json の数千行の差分に対して Claude が「この依存はメジャーバージョンが上がっています」と毎回言う。情報としては正しいが誰も求めていない。除外したら投稿コメント数が 約3割減、そのほとんどが誤検知側だった。トークンも減るので課金も下がる一石二鳥。
2) severity を high/medium の2値に絞り、low を出力スキーマから消した。 以前は low/info も許していて、Claude は親切なので必ず low を埋めてくる。スキーマから列挙ごと削ると、モデルは「言うほどでもない指摘」を構造的に出せなくなる。プロンプトで「重要なものだけ」と頼むより、JSON Schema の enum で物理的に塞ぐ ほうが効いた。
3) synchronize での再レビューを差分の差分にする。 これは上のスクリプトには入れていない発展形だが、pull_request.synchronize(追加 push)のたびに全 diff を再レビューすると、同じ指摘が PR に何度も付く。github.event.before と after で 今回の push 分の diff だけに絞ると、重複コメントが消える。140 PR/月の運用ではこれが体感で一番効いた。
コスト内訳も具体的に。Haiku 4.5 は入力が安く、平均 diff 8,000 トークン前後・出力 600 トークンで、1 PR あたり おおよそ 0.6〜1.2 円。Sonnet にエスカレーションする(severity high が出た PR だけ再検証する)二段構えにしても月 3,200 円に収まっている。GPT-4 系で同じことをやっていた頃の見積もりより安く、何より Haiku のレイテンシが速いので PR を開いてからコメントまで平均 35 秒 で返る。
ハマった落とし穴:行番号ズレと line vs start_line で 422
動かす人が必ず踏むので先に書く。GitHub の Review コメント API は、diff に含まれない行番号を指定すると HTTP 422(Unprocessable Entity)で全コメントごと弾く。Claude が稀に「変更されていない近くの行」を指してくると、その1件のせいでレビュー全体が投稿失敗する。対策は2つ。(a) スクリプト側で diff のハンクから有効行番号の集合を作り、含まれない line を捨てる。(b) reviews エンドポイントではなく pulls/{n}/comments を1件ずつ投げ、422 を個別に握りつぶす。私は重要な指摘を1件も落としたくなかったので (b) に寄せ、失敗したコメントだけ通常コメント(行なし)にフォールバックさせている。
もう一つ。fetch-depth: 0 を忘れると git diff {BASE}...{HEAD} が fatal: bad object で落ちる。Actions の checkout はデフォルトで浅いクローンなので base コミットが手元にない。地味だが最頻出の事故だ。
まず月140PRの自分のリポジトリで、Haiku 4.5の一次レビューから始めよ
最短の導入順はこうだ。(1) 上の YAML と review.py をコピーして ANTHROPIC_API_KEY を入れる。(2) 自分の個人リポジトリで適当な PR を開き、35秒待ってコメントが付くか確認。(3) 1週間運用してミュートされた指摘の傾向を見て、SYSTEM プロンプトと :(exclude) を自分のスタックに合わせて削る。(4) 効いてきたら severity high のみ Sonnet 再検証を足す。
Claude Code SDK と GitHub Actions の組み合わせは、CI に AI レビューを差すうえで現状もっとも安く速い選択肢だと実測で言える。是非あなたのリポジトリで動かしてみてほしい。
関連して、CI のコスト削減や AI 開発環境をさらに整えるなら、無料枠の大きいクラウドや高還元の開発者向けクレカで API 課金を実質オフセットする手もある。固定費を下げる導線は別記事にまとめている。