1
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?

[Mac][ClaudeCode Proプラン対応] Slackに貼ったGitHubのPRを、Claude Codeが自動でレビューしてスレッドに返すBotを作る

1
Last updated at Posted at 2026-10-09

目的

  • SlackにPRレビュー依頼 → 自動返信の仕組みを追加コストなしで実現
  • Claudecode proプランの定額内でやりたい

できること

  • Slackチャンネルに GitHub の PR の URL を投稿する
  • Bot が Claude Code(claude -p)を呼び出して PR をレビューする
  • レビュー結果を、Markdownのテキストスニペットとしてスレッドに返す

メリット

  • 最小限コスト(ClaudeCode pro プラン と ローカルPCのみ)で回せる
  • Claude Console の API(別料金)を使わないで済む

デメリット

  • ローカルPC(でbot.py)が動いてないと動作しない

構成

Slack(チャンネルに PR の URL を投稿)
  ↓ Socket Mode
bot.py(Mac 上で常駐、Python + Slack Bolt)
  ↓ subprocess
claude -p(gh pr view / gh pr diff で PR を読んでレビュー)
  ↓
Slack のスレッドにスニペットで返信

前提

  • Mac(ターミナルで作業)※Windowsでもいけると思う
  • Python 3
  • Claude Code がインストール済みで、Pro / Max プランにログインできる
  • GitHub CLI(gh)がインストール済み
  • Slack ワークスペースにアプリを追加できる権限

注意:claude -p はサブスクリプションの利用枠を消費する。チーム全体で使う場合は、Claude Console の API キーを使うほうが適している(コスト次第)。


1. Slack アプリを作る

  1. https://api.slack.com/apps を開き、Create New App → From a manifest を選ぶ。
  2. ワークスペースを選び、次の JSON を貼り付けて作成する。
{
  "display_information": { "name": "PrReviewBot" },
  "features": {
    "bot_user": { "display_name": "PrReviewBot", "always_online": true }
  },
  "oauth_config": {
    "scopes": {
      "bot": ["chat:write", "channels:history", "groups:history", "files:write"]
    }
  },
  "settings": {
    "event_subscriptions": {
      "bot_events": ["message.channels", "message.groups"]
    },
    "org_deploy_enabled": false,
    "socket_mode_enabled": true,
    "is_hosted": false,
    "token_rotation_enabled": false
  }
}
スコープ 用途
chat:write メッセージを送る
channels:history / groups:history 公開/プライベートチャンネルのメッセージを読む
files:write レビュー結果をスニペットで送る

2. トークンを2つ取得する

トークン 取得場所 先頭の文字
App-Level Token Basic Information → App-Level Tokens → Generate Token and Scopes(スコープは connections:write) xapp-
Bot User OAuth Token Install App → Install to Workspace → 表示されたトークン xoxb-

後からスコープを追加したときは、OAuth & Permissions 画面上部の reinstall your app を押してインストールし直す。

3. チャンネルに Bot を追加

  • チャンネル名をクリック → エージェントとアプリ → アプリを追加する → PrReviewBot を追加

同じ画面の 概要 タブの一番下にある チャンネルID(C0 で始まる文字列)を控えておく。

4. Claude Code と GitHub CLI の準備

# Claude Code 用の長期トークンを発行(sk-ant-oat01-... が表示される)
claude setup-token

# gh がログイン済みか確認(未ログインなら gh auth login)
gh auth status

5. 作業フォルダと Python 環境を作る

mkdir -p ~/pr-review-bot && cd ~/pr-review-bot
python3 -m venv venv
source venv/bin/activate
pip install -U slack_bolt slack_sdk

6. .env を作る

cat > .env <<'EOF'
export SLACK_BOT_TOKEN=xoxb-*********
export SLACK_APP_TOKEN=xapp-*********
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-*********
EOF
chmod 600 .env

.env は Git にコミットしないように(.gitignore に追加)。

7. bot.py を作る

TARGET_CHANNEL を、3. で控えたチャンネルIDに書き換える。

# pip install slack_bolt
import os, re, subprocess, json, time, threading
from datetime import datetime
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=os.environ["SLACK_BOT_TOKEN"])

TARGET_CHANNEL = "C0*********"  # 対象チャンネルのID

PR_RE = re.compile(r"https://github\.com/([\w.-]+/[\w.-]+)/pull/(\d+)")

PROMPT = """GitHubのPR {repo}#{num} をレビューしてください。
`gh pr view {num} --repo {repo}` と `gh pr diff {num} --repo {repo}` で内容を確認し、
機能・品質・セキュリティ・データ整合性・パフォーマンスの観点で、
日本語で簡潔に指摘してください。結論(Approve可/要修正)を最後に書いてください。"""


# ターミナルに時刻付きで経過を表示する
def log(msg):
    print(f"[{datetime.now():%H:%M:%S}] {msg}", flush=True)


@app.event("message")
def on_message(event, say, client):
    # 受け取ったメッセージをすべて表示する(どこで止まっているか調べるため)
    log(f"メッセージ受信:channel={event.get('channel')} bot_id={event.get('bot_id')} text={event.get('text', '')[:60]}")
    if event.get("channel") != TARGET_CHANNEL or event.get("bot_id"):
        return
    m = PR_RE.search(event.get("text", ""))
    if not m:
        return
    repo, num = m.group(1), m.group(2)
    ts = event["ts"]
    log(f"PRを検出:{repo}#{num}")
    say(text="レビューを開始します…", thread_ts=ts)

    # Claude Code を stream-json で起動し、動きを1行ずつ表示する
    start = time.time()
    proc = subprocess.Popen(
        ["claude", "-p", PROMPT.format(repo=repo, num=num),
         "--allowedTools", "Bash(gh pr view:*),Bash(gh pr diff:*)",
         "--output-format", "stream-json", "--verbose"],
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    timer = threading.Timer(900, proc.kill)  # 15分で強制終了
    timer.start()
    log("Claude Code を起動しました")

    review = ""
    error = ""
    for line in proc.stdout:
        try:
            ev = json.loads(line)
        except json.JSONDecodeError:
            log(f"  {line.rstrip()[:200]}")  # JSON以外(エラー文など)はそのまま表示
            error += line
            continue
        if ev.get("type") == "assistant":
            for c in ev["message"].get("content", []):
                if c.get("type") == "tool_use":
                    cmd = c.get("input", {}).get("command", "")
                    log(f"  実行中:{c.get('name')} {cmd}")
                elif c.get("type") == "text" and c.get("text", "").strip():
                    log(f"  考え中:{c['text'].strip()[:80]}…")
        elif ev.get("type") == "result":
            review = (ev.get("result") or "").strip()
            if ev.get("is_error"):
                error += review
                review = ""
    proc.wait()
    timer.cancel()
    elapsed = int(time.time() - start)
    log(f"Claude Code 終了({elapsed}秒, 終了コード {proc.returncode})")

    if not review:
        say(text=f"エラー:{error[:500] or 'タイムアウトまたは出力なし'}", thread_ts=ts)
        log("エラーをSlackに返信しました")
        return

    # Markdown のテキストスニペットとしてスレッドに送る
    client.files_upload_v2(
        channel=TARGET_CHANNEL,
        thread_ts=ts,
        content=review,
        filename=f"review-{repo.replace('/', '-')}-{num}.md",
        title=f"PRレビュー {repo}#{num}",
        snippet_type="markdown",
        initial_comment=f"レビューが終わりました({elapsed}秒)",
    )
    log("レビュー結果をSlackに送信しました")


if __name__ == "__main__":
    log("Botを起動します")
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

ポイント

  • --allowedTools:gh pr view と gh pr diff だけを許可しているので、許可を求める確認で止まらず、それ以外のコマンドも実行されません。
  • --output-format stream-json --verbose:Claude Code の動きが JSON で1行ずつ流れてくるので、実行中のコマンドをターミナルに表示できる。最後の "type": "result" の行の result にレビュー本文が入っている。
  • files_upload_v2(snippet_type="markdown"):長いレビューも、文字数制限で切れずにスニペットとして全文届きく。

8. 起動する

cd ~/pr-review-bot
source venv/bin/activate
source .env
python bot.py
[10:24:52] Botを起動します
⚡️ Bolt app is running!

と表示されれば起動できている。ターミナルを閉じると Bot も止まる。

9. 動作確認

チャンネルに PR の URL を投稿すると、ターミナルにこう表示される。

[10:25:30] メッセージ受信:channel=C0********* bot_id=None text=https://github.com/...
[10:25:30] PRを検出:owner/repo#123
[10:25:31] Claude Code を起動しました
[10:25:40]   実行中:Bash gh pr view 123 --repo owner/repo
[10:25:48]   実行中:Bash gh pr diff 123 --repo owner/repo
[10:27:10] Claude Code 終了(99秒, 終了コード 0)
[10:27:11] レビュー結果をSlackに送信しました

トラブルシューティング

症状 原因と対処
ターミナルに何も出ない Bot がチャンネルに入っていない、またはイベント設定の誤り
メッセージ受信 の channel が違う TARGET_CHANNEL を表示された ID に直す
メッセージ受信 だけで止まる URL が https://github.com/…/pull/数字 の形になっていない
Claude Code を起動しました で止まる CLAUDE_CODE_OAUTH_TOKEN を確認する
gh のところでエラー gh auth status でログインとリポジトリの権限を確認する
missing_scope エラー files:write を追加して、アプリを再インストールする

参考

1
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
1
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?