Claude Media
Claude CodeでSlack Botを作る — Bolt for JavaScriptとSocket Mode

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だけで動作確認まで進められます。ボタンやモーダルのJSONの作り方はClaudeにSlack Block KitのJSONを作らせる手順にあります。

この記事では、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に任せる部分と自分でやる部分に分けます。

手順

作業の流れと担当

  1. 1

    Slackアプリの作成とトークン発行(自分)

    スコープ追加とトークン発行はSlackの設定画面での操作で、トークンが画面に出ます。

  2. 2

    雛形と `app.js` のリスナー実装(Claude Code)

    npm init と @slack/bolt の導入、リスナーの実装です。手順が定型で、差分をレビューして取り込めます。

  3. 3

    トークンを環境変数に入れて起動(自分)

    トークンをClaudeに触らせないための分担です。

  4. 4

    起動ログの確認とSlackでの発話テスト(自分。ログはClaudeに貼る)

    動作確認はSlackの画面側で起きます。

この分担にする理由は、次の節のトークンの扱いにあります。

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のファイルツールが対象パスを読むのを止めます。同じパスへの編集と新規作成も止まります(編集はv2.1.208以降、書き込みはv2.1.228以降)。cat や head のようにBashで呼ぶ読み取りコマンドと、> file などのリダイレクト先にも適用されます。

書き方にも落とし穴があります。.claudeignore ファイルを置いても効果はなく、除外したいパスは Read のdenyルールに移します。Write(.env) のようにファイル書き込みツール名で書いたパスルールは、受け付けられるものの参照されません。編集を止めたいパスは Edit(...) で書きます。NotebookEdit は Read のdenyの対象外なので、ノートブックを守るなら Edit のdenyを足します。

ただし限界があります。ファイル名を指定せずに読む 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のガイドの流れに沿って、アプリ設定画面で次を進めます。

  1. アプリを新規作成し、ワークスペースを選ぶ
  2. OAuth & PermissionsのBot Token Scopesで chat:write を追加する
  3. Install to Teamでワークスペースにインストールし、xoxb- のBotトークンを控える
  4. Socket ModeでEnable Socket Modeをオンにする
  5. Basic InformationのApp-Level TokensでGenerate Token and Scopesを選び、connections:write スコープを付けて xapp- のトークンを控える
  6. 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のドキュメントには、設定画面の代わりにアプリマニフェストで同じ設定を宣言する例もあります。Socket Modeに関わるのは settings の次の部分です。

"settings": {
  "event_subscriptions": { "bot_events": ["app_mention"] },
  "org_deploy_enabled": false,
  "socket_mode_enabled": true,
  "is_hosted": false,
  "token_rotation_enabled": false
}

socket_mode_enabled が、画面のEnable Socket Modeに当たります。

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
claude

Claude 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() は、ここでは文字列 hello を含む発言に反応します。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! と表示されれば、起動はできています。イベントが届かないときは、スコープ・購読・再インストールを確認します。Slackで次を試します。

  1. Botとのダイレクトメッセージを開く。公開チャンネルで試すなら、Botを招待する
  2. hello と送り、ボタン付きの返信が来ることを確認する
  3. Click Meを押し、押した人へのメンション付き返信が来ることを確認する
  4. チャンネルで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- トークン

切断メッセージの実物

自前で受け口を書く場合は、WebSocketに流れてくる切断通知を読めると原因を切り分けられます。通知は type が disconnect のJSONで、reason で種類が分かります。

reason意味
warning意味切断の約10秒前に届く予告。届かないこともある
refresh_requested意味数時間に一度の接続の更新
link_disabled意味アプリ設定でSocket Modeをオフにした
{ "type": "disconnect", "reason": "refresh_requested",
  "debug_info": { "host": "wss-111.slack.com" } }

接続後に届く hello メッセージの debug_info.approximate_connection_time(秒)は、接続がどれくらい続くかの見積もりに使えます。接続の更新時に受信が途切れないよう、更新の前に2本目の接続を張っておく使い方ができます。ペイロードがどの接続に届くかは決まっていないため、順序には頼れません。

BoltなどのSlack製SDKを使えば、WebSocket URLの取得(apps.connections.open)や切断後の再接続を任せられます。自前で実装するのは、SDKが使えない事情があるときだけで十分です。

社内の限られたワークスペースで動かすBotなら、Socket Modeのまま常駐させる選択肢もあります。公開配布や、大きな組織で安定して受けたい場合は、公開HTTPのRequest URLに切り替えます。HTTPは、ホスティング環境への展開やMarketplace配布向きとされています。切り替えはアプリ設定でいつでもできます。Socket Modeを有効にすると、イベントとインタラクティブなペイロードはWebSocketだけで届き、HTTPには届きません。HTTPから切り替える瞬間には、イベントを取りこぼす可能性があります。

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がトークンを読めてしまう。環境変数として渡したトークンは、denyでは守れません。トークンを環境変数に入れて渡す起動は自分のターミナルで行い、Claudeには起動ログだけを渡します。

Claudeが CLAUDE.md のルールを守らない。 /context で読み込まれているかを見て、指示を具体的に書き直します。守らせたい処理は、CLAUDE.md でなくhookに移します。

まとめ

Slack Botの自作は、Slack側の設定を人間が、コードをClaude Codeが受け持つと進めやすくなります。公開URLなしで hello への返信、ボタン、メンションまで確認できます。

社内限定のBotならSocket Modeのまま常駐させ、配布や大きな組織向けならHTTPのRequest URLへ移す、という分け方になります。同じ流れの別サービス版は、Discord BotとLINE Botの記事にもあります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.slack.dev/tools/bolt-js/creating-an-app
  2. docs.slack.dev/tools/bolt-js/concepts/socket-mode
  3. docs.slack.dev/apis/events-api/using-socket-mode
  4. code.claude.com/docs/en/memory
  5. code.claude.com/docs/en/permissions
この記事を共有:XはてブLinkedIn