0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【2026最新版】GitHub ActionsでClaude Code SDKを動かす自動PRレビューの実装:月3,200円・誤検知率を42%下げた設定全公開

0
Posted at

⚠️ この記事はアフィリエイト広告(プロモーション)を含みます。リンク先で発生した収益の一部が運営者に支払われますが、読者の購入価格には一切影響ありません。

結論から言うと、このコピペで「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

ポイントは permissionspull-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 diffbase...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_choicepost_review を強制 している点が肝で、これにより「了解しました、レビューします」みたいな前置きテキストが混ざらず、JSON パース失敗がゼロになった。最初は普通の文章で返させて正規表現で JSON を抜いていたが、20本に1本くらい Markdown のコードフェンスを付けてきて壊れた。tool 強制にしてからその事故は消えた。

誤検知を71%→41%に下げた、Claude設定の3つの実変更

ここが本題。最初の実装は「全部見て気づいたこと全部言って」だった。結果、変数名やコメント不足の指摘が洪水になり、開発者がミュートし始めた。次の3つで実用域に入った。

1) git diff:(exclude) で lock とスナップショットを物理排除。 package-lock.json の数千行の差分に対して Claude が「この依存はメジャーバージョンが上がっています」と毎回言う。情報としては正しいが誰も求めていない。除外したら投稿コメント数が 約3割減、そのほとんどが誤検知側だった。トークンも減るので課金も下がる一石二鳥。

2) severityhigh/medium の2値に絞り、low を出力スキーマから消した。 以前は low/info も許していて、Claude は親切なので必ず low を埋めてくる。スキーマから列挙ごと削ると、モデルは「言うほどでもない指摘」を構造的に出せなくなる。プロンプトで「重要なものだけ」と頼むより、JSON Schema の enum で物理的に塞ぐ ほうが効いた。

3) synchronize での再レビューを差分の差分にする。 これは上のスクリプトには入れていない発展形だが、pull_request.synchronize(追加 push)のたびに全 diff を再レビューすると、同じ指摘が PR に何度も付く。github.event.beforeafter今回の 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 課金を実質オフセットする手もある。固定費を下げる導線は別記事にまとめている。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?