Claude CodeでSlack Botを作る — Bolt for JavaScriptとSocket Mode
Claude CodeにBolt for JavaScriptのSlack Botを書かせ、Socket Modeでローカル検証するまでの手順。CLAUDE.mdとdenyでトークンを守る設定も扱います。
Claude CodeでSlack Botを作る流れ
Slack Botを自作するなら、SlackのBolt for JavaScriptとSocket Modeの組み合わせが最短です。Socket Modeは公開URLなしでイベントを受け取れるため、手元のPCだけで動作確認まで進められます。
この記事では、Claude Codeにプロジェクトの雛形とイベントリスナーを書かせ、自分のターミナルでBotを起動して確認するところまでを扱います。Slack側の設定は人間が画面で行い、コードはClaude Codeに任せる分担です。もうひとつ、トークンをClaudeの手が届かない場所に置くための CLAUDE.md とdeny設定も具体的に書きます。
Botの中でClaudeを呼び出して返答させる常駐型は、Agent SDKでSlack常駐botを作る記事が対象です。ここで作るのは、Claude Codeが開発を手伝う側のBotです。
前提条件と役割分担
必要なものは次のとおりです。
- Node.js(最新のアクティブリリースが推奨されています)
- Slackワークスペース(本番の業務用ではなく、開発用サンドボックスが勧められています)
- Claude Codeが使える環境
作業は、Claude Codeに任せる部分と自分でやる部分に分けます。
| 作業 | 担当 | 理由 |
|---|---|---|
| Slackアプリの作成・スコープ追加・トークン発行 | 担当自分 | 理由Slackの設定画面での操作で、トークンが画面に出る |
npm init と @slack/bolt の導入 | 担当Claude Code | 理由手順が定型で、結果もファイルで確認できる |
app.js のリスナー実装 | 担当Claude Code | 理由差分をレビューして取り込める |
| トークンを環境変数に入れて起動 | 担当自分 | 理由トークンをClaudeに触らせない |
| 起動ログの確認とSlackでの発話テスト | 担当自分(ログはClaudeに貼る) | 理由動作確認はSlackの画面側で起きる |
「起動は自分の端末で行い、ログだけをClaudeに渡す」のが、この記事の要点です。理由は次の節で説明します。
CLAUDE.mdとdenyでトークンを守る
Slack Botには2種類のトークンが要ります。xoxb- で始まるBotトークンと、Socket Modeで使う xapp- のアプリレベルトークンです。トークンは公開リポジトリに入れず、環境変数経由で使うのが前提です。
Claude Codeに開発を任せるなら、トークンを書いたファイルをClaudeが読まないようにしておくのが安全です。順に設定します。
deny設定でファイルの読み取りを止める
プロジェクトの .claude/settings.json に、Read のdenyルールを書きます。
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets/**)"
]
}
}Read のdenyルールは、Claudeのファイルツールが対象パスを読むのを止めます。編集と新規作成も同じパスでは止まります。cat や head のようにBashで呼ぶ読み取りコマンドにも適用され、> file などのリダイレクト先にも効きます。
ただし限界があります。ファイル名を指定せずに読む grep -r pattern . や、Nodeのスクリプトが自分でファイルを開く場合は対象外です。Bashのdenyルールも、同じコマンドを別の形で呼ばれると素通りします。プロセス全体からパスを隔離したいときは、サンドボックスを有効にします。denyは「うっかり」を防ぐ設定と割り切ってください。
CLAUDE.mdに作業のルールを書く
プロジェクト直下の CLAUDE.md には、Claudeに守ってほしい開発ルールを書きます。次のような内容が一例です。
# Slack Bot プロジェクト
## 構成
- Node.js(ESM)、Bolt for JavaScript(@slack/bolt)
- エントリポイントは app.js。Socket Modeで動かす
## コマンド
- 構文チェック: node --check app.js
- 起動: 人間が自分のターミナルで node app.js を実行する
## トークンの扱い
- SLACK_BOT_TOKEN と SLACK_APP_TOKEN は環境変数から読む
- トークンの値をコード・ログ・コミットに書かない
- .env と secrets/ は読まない。値が必要なら人間に頼む
## 実装ルール
- リスナーは async 関数にし、イベント単位で関数を分ける
- ボタン操作は ack() を最初に呼ぶCLAUDE.md の内容はセッション開始時にコンテキストへ読み込まれる指示で、強制力のある設定ではありません。曖昧な指示や矛盾する指示は、守られないことがあります。具体的で検証できる書き方にし、200行以内に収めるのが目安です。
「必ず守らせたいこと」は役割を分けます。読ませたくないファイルはdeny、コミット前に必ず走らせたい処理はhookです。CLAUDE.md は、前者と後者では拾えない方針を伝える場所と考えると整理しやすくなります。/context を実行すると、CLAUDE.md が読み込まれているかを確認できます。
手順1: Slackアプリを作る
ここは人間の作業です。Bolt for JavaScriptのガイドの流れに沿って、アプリ設定画面で次を進めます。
- アプリを新規作成し、ワークスペースを選ぶ
- OAuth & PermissionsのBot Token Scopesで
chat:writeを追加する - Install to Teamでワークスペースにインストールし、
xoxb-のBotトークンを控える - Socket ModeでEnable Socket Modeをオンにする
- Basic InformationのApp-Level TokensでGenerate Token and Scopesを選び、
connections:writeスコープを付けてxapp-のトークンを控える - Event Subscriptionsでイベントを有効にし、Botイベントを購読する
購読するBotイベントは、動かしたい場所で選びます。
| イベント | 拾う対象 |
|---|---|
message.channels | 拾う対象Botが参加している公開チャンネルの発言 |
message.groups | 拾う対象Botが参加している非公開チャンネルの発言 |
message.im | 拾う対象BotとのDM |
message.mpim | 拾う対象Botが参加している複数人DM |
DMだけで試すなら message.im で足ります。あとでメンションにも反応させるので、app_mention イベントの購読と app_mentions:read スコープも足しておきます。
スコープやイベントを追加したあとは、アプリの再インストールが必要です。Install Appページから入れ直します。これを忘れると、コードが正しくてもイベントが届きません。
Slack CLIの slack create でBoltのテンプレートを作る方法もあります。クイックスタートによると、テンプレートはSocket Mode前提で構成されています。この記事はNode.jsだけで進める手順で書きます。
手順2: プロジェクトの雛形をClaude Codeに作らせる
空のディレクトリでClaude Codeを起動します。トークンはまだ環境変数に入れません。先に、前の節の .claude/settings.json と CLAUDE.md を置いておきます。
mkdir first-bolt-app && cd first-bolt-app
git init
claudeClaude Codeには、たとえば次のように頼みます。
Bolt for JavaScriptのSlack Botの雛形を作って。
- npm init と npm pkg set type=module で ESM にする
- @slack/bolt を導入する
- app.js に、SLACK_BOT_TOKEN と SLACK_APP_TOKEN を環境変数から読み、
Socket Modeで起動する最小の App を書く
- .gitignore に .env と secrets/ と node_modules/ を入れるBoltのガイドが示すSocket Mode用の最小構成は、次の形です。Claude Codeが書いたコードも、この形と見比べてレビューします。
import { App } from "@slack/bolt";
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
socketMode: true,
appToken: process.env.SLACK_APP_TOKEN,
});
(async () => {
await app.start();
app.logger.info("⚡️ Bolt app is running!");
})();socketMode: true と appToken を渡すだけで、Boltが内部でSocket Mode用のレシーバーを使います。公開URLもポート指定も要りません。HTTPで受ける場合は署名シークレットとポートが必要になり、/slack/events というパスをRequest URLに登録する手順が加わります。
レビューで見るのは、トークンがコードに直書きされていないことと、process.env 経由になっていることの2点です。
手順3: イベントリスナーを実装させる
雛形ができたら、リスナーを足すよう頼みます。Boltでは、app.message() で発言、app.action() でボタン操作、app.event() で任意のイベントを受けます。
app.js に次のリスナーを追加して。
1. "hello" を含む発言に、ボタン付きのメッセージで返す(action_id は button_click)
2. button_click が押されたら、押した人をメンションして返信する
3. app_mention イベントで、メンションされたら挨拶を返す
CLAUDE.md の ack() ルールに従って。ガイドの例に沿うと、実装の骨格は次のようになります。これはサンプルに沿った形の例で、Claude Codeの実際の出力とは限りません。
// "hello" を含む発言に、ボタン付きで返す
app.message("hello", async ({ message, say }) => {
await say({
blocks: [
{
type: "section",
text: { type: "mrkdwn", text: `Hey there <@${message.user}>!` },
accessory: {
type: "button",
text: { type: "plain_text", text: "Click Me" },
action_id: "button_click",
},
},
],
text: `Hey there <@${message.user}>!`,
});
});
// ボタンが押されたら返信する
app.action("button_click", async ({ body, ack, say }) => {
await ack();
await say(`<@${body.user.id}> clicked the button`);
});
// メンションに反応する
app.event("app_mention", async ({ event, say }) => {
await say(`Hi <@${event.user}>!`);
});3つのリスナーには、それぞれ確認の勘所があります。
app.message() は、文字列のほか正規表現も受け付けます。say() は、イベントが起きたチャンネルへ返信する関数です。blocks を使うときの text は、通知とアクセシビリティ用の代替テキストとして働きます。
app.action() は、action_id でボタンと結び付きます。リスナーの冒頭で ack() を呼び、Slackに受信を伝えます。CLAUDE.md に書いた「ack() を最初に呼ぶ」ルールは、ここで効きます。
app.event() は、購読しているイベントの種類を文字列で指定します。購読していないイベントは、リスナーを書いても呼ばれません。
書き終えたら、Claude Codeに構文チェックを走らせます。
node --check app.jsこれはトークン不要で、Claude Codeが自分で実行できます。
手順4: Socket Modeで動作確認する
ここからは自分のターミナルに移ります。Claude Codeのセッションとは別のウィンドウで、トークンを環境変数に入れて起動します。
export SLACK_BOT_TOKEN=xoxb-<your-bot-token>
export SLACK_APP_TOKEN=xapp-<your-app-token>
node app.js⚡️ Bolt app is running! と表示されれば、WebSocketの接続まで通っています。Slackで次を試します。
- Botとのダイレクトメッセージを開く。公開チャンネルで試すなら、Botを招待する
helloと送り、ボタン付きの返信が来ることを確認する- Click Meを押し、押した人へのメンション付き返信が来ることを確認する
- チャンネルでBotをメンションし、挨拶が返ることを確認する
うまく動かないときは、ターミナルのログをClaude Codeに貼って原因を聞きます。トークンの値が含まれていないかは、貼る前に自分で確認してください。ログを見たClaudeは、リスナーの条件やスコープの不足を候補として挙げてくれます。Slackの設定画面側の問題(イベントの購読漏れ、再インストール忘れ)は、コードの修正では直らない点に注意が必要です。
停止は Ctrl+C です。コードを直すたびに、起動し直します。
Socket Modeはどこまで使えるか
Socket Modeは開発と検証に向く一方、ドキュメントに明記された制約があります。
| 項目 | 内容 |
|---|---|
| 公開URL | 内容不要。Slackがランタイムで作るWebSocket URLで通信する |
| 接続の更新 | 内容数時間に一度、接続の更新が起きる。切断のたびに再接続が要る |
| 同時接続数 | 内容1つのアプリで最大10本まで持てる |
| Slack Marketplace | 内容Socket Modeを使うアプリは、公開Marketplaceに載せられない |
| 必要なトークン | 内容connections:write スコープ付きの xapp- トークン |
BoltなどのSlack製SDKを使えば、WebSocket URLの取得(apps.connections.open)や切断後の再接続を任せられます。自前で実装するのは、SDKが使えない事情があるときだけで十分です。
社内の限られたワークスペースで動かすBotなら、Socket Modeのまま常駐させる選択肢もあります。公開配布や、大きな組織で安定して受けたい場合は、公開HTTPのRequest URLに切り替えます。HTTPは、ホスティング環境への展開やMarketplace配布向きとされています。切り替えはアプリ設定でいつでもできます。
Claude Code側の連携(/install-slack-app などSlackからClaudeを呼ぶ機能)は、Botを自作する話とは別の仕組みです。使い分けはClaude TagとClaude CodeのSlack連携の違いに、導入手順は/install-slack-appの記事にあります。
よくあるつまずき
Botが何も返さない。購読イベントと再インストールを確認します。message.* を足したあとにInstall Appから入れ直していないと、イベントが届きません。DMなら message.im、公開チャンネルなら message.channels と、Botのチャンネル参加が要ります。
起動時にトークンのエラーが出る。 xoxb- と xapp- の取り違えが定番です。token にはBotトークン、appToken にはアプリレベルトークンを渡します。xapp- トークンに connections:write スコープがなければ、Socket Modeの接続を開けません。
ボタンを押すと「失敗」になる。 app.action() の中で ack() を呼んでいるか、action_id が送信側と一致しているかを見ます。
Claude Codeがトークンを読めてしまう。 Read(./.env) のdenyは、Claudeのファイルツールとファイル読み取りコマンドを止めます。環境変数として渡したトークンや、スクリプトが自分で開くファイルは対象外です。だからこの記事では、起動を自分のターミナルで行う構成にしています。より確実に隔離したい場合は、サンドボックスを検討します。
Claudeが CLAUDE.md のルールを守らない。 /context で読み込まれているかを見て、指示を具体的に書き直します。守らせたい処理は、CLAUDE.md でなくhookに移します。
まとめ
Slack Botの自作は、Slack側の設定を人間が、コードをClaude Codeが受け持つと進めやすくなります。Boltは socketMode: true と appToken だけでSocket Modeに対応するので、公開URLなしで hello への返信、ボタン、メンションまで確認できます。
トークンの扱いは設計の要です。denyで .env を読ませず、CLAUDE.md に手順を明記し、起動は自分の端末で行う。この三点でClaudeに任せる範囲が定まります。同じ流れの別サービス版は、Discord BotとLINE Botの記事にもあります。