🥬

Claude CodeとSlack上で対話できるSlack Botを作った話

に公開

きっかけ

  • サクッと調べたいときにClaude CodeをSlack経由で使いたい
  • 1つのレポジトリに拘らず複数のレポジトリや他のデータソースも統合してClaude Codeで調べてもらいたい
    • アプリケーションのレポジトリ
    • お客様からのご質問への回答の知見(CS対応)
    • エンプラ企業から問い合わせが来たときのセキュリティチェックシートの知見
    • データベース
    • アクセスログ
  • Claude CodeのおかげでSlack Bot初心者でもSlack Botを爆速で作れそう
  • Claude Code ActionがCLAUDE_CODE_OAUTH_TOKENで認証できるようになり、Claude Maxプランに対応してくれるようになった

その結果、複数のレポジトリをクローンしたり各データソースと連携して横断して調査してくれるClaude Code Github Actions用のレポジトリを作って、かつSlack上で操作できるようにしたい、となりました。

完成したシステム

Slackから自然言語で質問すると、Claude Codeが複数のリポジトリを参照して回答するSlack AIボットです。プレーリーカードの非公式キャラクターとして、「キャベツ」という名前を付けています。

アーキテクチャ

[Slack] → [AWS Lambda + Serverless Framework] → [GitHub Issues] → [GitHub Actions + Claude Code] → [Slack通知]

GUIでのセットアップ

このシステムを構築するには複数のサービスとの連携が必要でした。主要なセットアップを簡潔にまとめます。Claude CodeにGUIの手順まで聞くとちゃんと教えてくれて、言われた通りポチポチするだけでした。

1. Slackアプリの作成と設定

基本設定

  1. Slack APIで「Create New App」→「From scratch」
  2. Bot User作成: 「App Home」→「Edit」→Display Name設定
  3. OAuth権限設定: app_mentions:read, chat:write, im:historyなど
  4. Event Subscriptions: app_mention, message.imを追加
  5. デプロイ後のエンドポイントURLをSlack AppのEvent SubscriptionsのRequest URLに設定。

重要ポイント

  • Messages TabでDMを有効化する設定を忘れずに
  • Bot User OAuth Token (xoxb-...)とSigning Secretをメモ

2. GitHub Personal Access Token

Claude Code ActionはGithub Appから起動に対応しない制約があり、GitHub AppではなくPATが必要でした。

  1. GitHub Settings > Tokens
  2. 生成されたトークン(ghp-...)を安全に保存

3. Claude Code OAuth Token

  1. claude setup-tokenで生成

実装の詳細

記事が膨れるので、レポジトリ以外のデータソースの参照は省略します。

メインのSlack Bot実装 (app.js)

Slack Bolt, AWS Lambda, Serverless Frameworkを使用したSlack Bot。メンション・DM対応、スレッド管理、GitHub Issue連携、Slackリトライ対策を実装。 Slack公式記事をClaude Codeに食わせて、大半を作らせてもらってから調整しました。

const { App, AwsLambdaReceiver } = require("@slack/bolt")
const GitHubIssueManager = require("./lib/github-issue-manager")

// Initialize the receiver for AWS Lambda
const awsLambdaReceiver = new AwsLambdaReceiver({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
})

// Initialize the Slack app
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  receiver: awsLambdaReceiver,
  processBeforeResponse: true,
  ignoreSelf: true,
})

// Initialize GitHub Issue Manager
const githubManager = new GitHubIssueManager({
  githubToken: process.env.GITHUB_TOKEN,
  owner: process.env.GITHUB_OWNER,
  repo: process.env.GITHUB_REPO,
})

// Listen to mentions of the app
app.event("app_mention", async ({ event, say, client }) => {
  try {
    const threadTs = event.thread_ts || event.ts
    let threadMessages = []

    // 既存のIssueがあるか確認
    const existingIssueNumber = await githubManager.findIssueByThreadId(threadTs)

    // 新規Issue作成時のみスレッド全体を取得(スレの途中でメンションされた場合に遡って情報が必要なので)
    if (!existingIssueNumber && event.thread_ts) {
      const threadHistory = await client.conversations.replies({
        channel: event.channel,
        ts: threadTs,
        inclusive: true,
        limit: 100,
      })
      threadMessages = threadHistory.messages || []
    }

    const slackData = {
      channel: event.channel,
      threadTs: threadTs,
      user: event.user,
      text: event.text,
      eventTs: event.ts,
      threadMessages: threadMessages,
    }

    const result = await githubManager.processSlackMessage(slackData)
    const issueNumber = result.number

    await say({
      text: existingIssueNumber
        ? `追加のご質問ですね!少々お待ちを!`
        : `受付番号 #${issueNumber}\nご質問ありがとうございます!調査を開始します。`,
      thread_ts: event.ts,
    })
  } catch (error) {
    console.error("Failed to create/update GitHub issue:", error)
    await say({
      text: `エラーが発生しました。お問い合わせください。`,
      thread_ts: event.ts,
    })
  }
})

// Slackリトライを検出して無視
module.exports.handler = async (event, context, callback) => {
  if (event.headers && event.headers["X-Slack-Retry-Num"]) {
    return {
      statusCode: 200,
      body: JSON.stringify({ message: "Retry ignored" }),
    }
  }
  const handler = await awsLambdaReceiver.start()
  return handler(event, context, callback)
}

Claude Code Actionワークフロー (.github/workflows/claude-issue-handler.yml)

issueで発火して、複数リポジトリをcloneし、Claude Code Actionで調査実行後、結果をSlackに送信するワークフロー。

name: Claude Issue Handler

permissions:
  contents: write
  pull-requests: write
  issues: write
  id-token: write

on:
  issues:
    types: [opened]
  issue_comment:
    types: [created]

concurrency:
  # 調査中に別のコメントが来たらキャンセルして新しい方のみを動かす
  group: claude-issue-${{ github.event.issue.number }}
  cancel-in-progress: true

jobs:
  claude-issue-handler:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # 複数リポジトリをクローン
      - name: Checkout knowledge repository
        uses: actions/checkout@v4
        with:
          repository: company/knowledge-base
          path: knowledge
          token: ${{ secrets.CROSS_REPO_PAT }}
          fetch-depth: 0

      - name: Checkout main app repository
        uses: actions/checkout@v4
        with:
          repository: company/main-app
          path: main_app
          token: ${{ secrets.CROSS_REPO_PAT }}
          fetch-depth: 0

      - name: Get issue with all comments
        id: get_issue_data
        run: |
          gh issue view ${{ github.event.issue.number }} --comments > /tmp/issue_full.txt
          echo "issue_full<<EOF" >> $GITHUB_OUTPUT
          cat /tmp/issue_full.txt >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Claude Code Action実行
      - name: Setup Claude Code Action
        id: claude_investigation
        uses: anthropics/claude-code-action@beta
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
        with:
          direct_prompt: |
            あなたの唯一の目的:Issue #${{ github.event.issue.number }}への回答をSlackで送信すること。
            そのためにtemp/slack-message.jsonファイルを作成してください。
            詳細は`/docs/github-actions-guide.md`参照。
            Issue内容: ${{ steps.get_issue_data.outputs.issue_full }}
          claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          timeout_minutes: 30
          allowed_tools: |
            Edit
            Write

      # Slack JSONをGitHub Issueに記録して、コメントの往復時に何を送ったかClaudeが参照できるように
      - name: Add Slack message JSON to Issue comment
        if: always()
        run: |
          echo "### Slackに送信したメッセージ内容" >> comment_body.md
          echo '```json' >> comment_body.md
          cat temp/slack-message.json >> comment_body.md
          echo '```' >> comment_body.md
          gh issue comment ${{ github.event.issue.number }} --body-file comment_body.md
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # 回答を実際にSlackに送信
      - name: Send Slack notification
        if: always()
        run: |
          npm run send-slack
          echo "📤 Slack notification sent"
          rm -f temp/slack-message.json
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Claude Code Action制御ガイド (docs/github-actions-guide.md)

Claude Code Actionが確実にSlack送信まで完了するよう設計されたプロンプト。Slackメッセージ送信関数の引数となるtemp/slack-message.json作成を唯一の目的として明確化。

# GitHub Actions専用ガイド

**🎯 主目的**: あなたの唯一の目的は、Issue質問への回答をSlackで送信することです。
そのためにtemp/slack-message.jsonファイルの作成が必須です。

## 実行手順(Slack送信が最優先)

**最重要タスク**: temp/slack-message.jsonファイルの作成とSlack送信

1. Issue本文から「Channel ID: XXXXXX」と「Thread ID: XXXXXX」を抽出
2. Issue質問に対する回答を作成(必要に応じて関連リポジトリを参照)
   - セキュリティ・インフラ関連: knowledge/ディレクトリ
   - アプリケーション仕様・機能関連: main_app/ディレクトリ
3. 以下のフォーマットでtemp/slack-message.jsonに保存:

{
  "channel": "抽出したChannel ID",
  "thread_ts": "抽出したThread ID", 
  "blocks": [Block Kitメッセージ]
}

**🎯 成功条件**: temp/slack-message.jsonファイルが正しく作成されること。
これがあなたの唯一かつ最重要の成果物です。

**📊 完了報告**: 作業完了時にファイルサイズを報告してください:
✅ タスク完了: temp/slack-message.json作成 ([ファイルサイズ]KB)

## 知識ベース活用ガイド

### 参照判断基準
- **knowledgeリポジトリ**: セキュリティ、プライバシー、インフラ、コンプライアンス関連の質問
- **main_appリポジトリ**: アプリケーションの仕様、動作、機能、実装に関する質問

### 活用方法
1. Issue質問内容を分析
2. 上記判断基準に該当する場合、適切なリポジトリを参照
   - セキュリティ・インフラ関連 → `knowledge/docs/`
   - アプリケーションの仕様・機能関連 → `main_app/`
3. 正確で具体的な情報を含めて回答作成

躓いたこと

  • Slack送信を唯一の主目的として明確化する必要があった。結構忘れられた
  • Github Appは使えず、Personal Access Tokenを使う必要があった
  • Claude Code Actionの早期終了を防ぐプロンプト改善が必要だった
    • max_turns制限を削除してClaude Code Actionの完全実行を確保する必要があった
    • 品質重視で段階を踏ませるプロンプト(調査、計画、実行etc)にすると、何度も途中で完了扱いで終わってしまった
  • Slackは3秒以内にレスポンスを返さないと、リトライによる重複Issue作成が起きてしまい、コールドスタートのlambdaには辛く、重複を防ぐ必要があった
  • プロンプトインジェクション対策で複数レポジトリのみ参照する段階では任意のBash実行を許可してない。temp/slack-message.jsonを作成して、後続ステップでそれを使いSlackに送信するようにした

Discussion