Backlog 問い合わせ課題を Claude で自動分析してコメント投稿する構成

Backlog を問い合わせ管理に使っていると、課題が登録されるたびに内容を確認し、分類や初期対応を行う作業が発生します。この作業を Claude に任せ、課題が追加された瞬間に自動で分析コメントを投稿する仕組みを構築します。

全体構成

┌──────────┐     Webhook      ┌─────────────┐     invoke     ┌──────────┐
│ Backlog  │ ──── JSON ────→ │ API Gateway │ ────────────→ │  Lambda  │
│ (課題追加) │                  │             │               │ (受信)    │
└──────────┘                  └─────────────┘               └────┬─────┘
                                                                 │
                                                            SQS enqueue
                                                                 │
                                                                 ▼
┌──────────┐    コメント投稿    ┌─────────────┐   Claude API   ┌──────────┐
│ Backlog  │ ◀── POST ────── │   Lambda    │ ◀──────────── │  Amazon  │
│ (課題)    │                  │  (処理)      │               │ Bedrock  │
└──────────┘                  └─────────────┘               └──────────┘

なぜ 2 段構成(Lambda + SQS)なのか

Backlog の Webhook は 10 秒以内に HTTP 200 を返さないとタイムアウトし、失敗として再送を繰り返します。Claude の分析には数十秒かかるため、受信 Lambda は即座に 200 を返し、SQS を介して処理 Lambda に非同期で委譲します。

前提条件

項目内容
AWS アカウントLambda, API Gateway, SQS, Bedrock 利用可能
Backlogスタンダードプラン以上(Webhook 機能が必要)
Claude モデルAmazon Bedrock で Claude Sonnet を利用
IaCAWS CDK(Python)で構築

Step 1: Backlog Webhook の設定

Webhook が送信する JSON

課題追加時に Backlog が POST する JSON の主要フィールド:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "id": 12345,
  "project": {
    "id": 1,
    "projectKey": "SUPPORT",
    "name": "サポート"
  },
  "type": 1,
  "content": {
    "id": 6789,
    "key_id": 42,
    "summary": "ログインできません",
    "description": "2026年3月1日からログインページでエラーが出ます...",
    "issueType": { "id": 2, "name": "問い合わせ" },
    "priority": { "id": 3, "name": "中" },
    "status": { "id": 1, "name": "未対応" }
  },
  "createdUser": {
    "id": 100,
    "name": "田中太郎"
  },
  "created": "2026-03-02T10:00:00Z"
}

イベントタイプ

typeイベント
1課題の追加
2課題の更新
3コメントの追加
14課題のまとめて更新

今回は type: 1(課題の追加)をトリガーにします。

Step 2: 受信 Lambda(webhook_receiver)

Webhook を受信し、バリデーション後に SQS へエンキューします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# webhook_receiver/handler.py
import json
import os
import boto3

sqs = boto3.client("sqs")
QUEUE_URL = os.environ["SQS_QUEUE_URL"]
WEBHOOK_TOKEN = os.environ.get("BACKLOG_WEBHOOK_TOKEN", "")
BOT_USER_ID = int(os.environ.get("BOT_USER_ID", "0"))

def handler(event, context):
    """Backlog Webhook を受信し、SQS にエンキュー"""

    # トークン検証
    params = event.get("queryStringParameters") or {}
    if WEBHOOK_TOKEN and params.get("token") != WEBHOOK_TOKEN:
        return {"statusCode": 403, "body": "Forbidden"}

    body = json.loads(event["body"])

    # 課題追加(type=1)のみ処理
    if body.get("type") != 1:
        return {"statusCode": 200, "body": "Skipped: not issue creation"}

    # 無限ループ防止: Bot 自身が作成した課題はスキップ
    created_user_id = body.get("createdUser", {}).get("id")
    if BOT_USER_ID and created_user_id == BOT_USER_ID:
        return {"statusCode": 200, "body": "Skipped: bot user"}

    # SQS にエンキュー
    sqs.send_message(
        QueueUrl=QUEUE_URL,
        MessageBody=json.dumps(body, ensure_ascii=False),
    )

    return {"statusCode": 200, "body": "OK"}

ポイント:

  • 10 秒以内に 200 を返すため、処理は SQS に委譲
  • BOT_USER_ID で自身が作成した課題をスキップ(無限ループ防止)
  • トークン検証で不正な呼び出しを排除

Step 3: 処理 Lambda(agent_invoker)

SQS からメッセージを取得し、Claude で分析してコメントを投稿します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# agent_invoker/handler.py
import json
import os
import re
import urllib.request
import urllib.parse
import boto3

bedrock = boto3.client("bedrock-runtime")
BACKLOG_SPACE = os.environ["BACKLOG_SPACE"]       # e.g. "yourspace"
BACKLOG_API_KEY = os.environ["BACKLOG_API_KEY"]
MODEL_ID = os.environ.get("MODEL_ID", "anthropic.claude-sonnet-4-20250514-v1:0")

def handler(event, context):
    """SQS メッセージを処理し、Claude で分析してコメント投稿"""

    for record in event["Records"]:
        body = json.loads(record["body"])
        process_issue(body)

def process_issue(webhook_data):
    """課題を分析してコメントを投稿"""

    content = webhook_data["content"]
    issue_id = content["id"]
    issue_key = f'{webhook_data["project"]["projectKey"]}-{content["key_id"]}'

    # Backlog API から課題の詳細を取得(Webhook データの補完)
    issue_detail = backlog_get(f"/api/v2/issues/{issue_key}")

    # Claude で分析
    analysis = invoke_claude(issue_detail)

    # 分析結果をコメントとして投稿
    backlog_post_comment(issue_key, analysis)

def invoke_claude(issue):
    """Claude で問い合わせ内容を分析"""

    prompt = build_prompt(issue)

    request_body = json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 2048,
        "temperature": 0.3,
        "messages": [{"role": "user", "content": prompt}],
    })

    response = bedrock.invoke_model(
        modelId=MODEL_ID,
        contentType="application/json",
        accept="application/json",
        body=request_body,
    )

    result = json.loads(response["body"].read())
    return result["content"][0]["text"]

def build_prompt(issue):
    """分析用プロンプトを構築"""

    summary = issue.get("summary", "")
    description = issue.get("description", "")
    issue_type = issue.get("issueType", {}).get("name", "")
    priority = issue.get("priority", {}).get("name", "")
    created_user = issue.get("createdUser", {}).get("name", "")

    return f"""<issue_info>
課題タイプ: {issue_type}
優先度: {priority}
報告者: {created_user}
件名: {summary}
本文:
{description}
</issue_info>

<instruction>
あなたはカスタマーサポートの分析アシスタントです。
上記の問い合わせ内容を分析し、以下の形式でレポートを作成してください。

## 分類
問い合わせの種類を判定してください(障害報告 / 機能要望 / 使い方の質問 / その他)

## 要約
問い合わせ内容を 2-3 行で要約してください。

## 影響範囲
推定される影響範囲(特定ユーザー / 複数ユーザー / 全体)を判定してください。

## 推奨対応
初期対応として推奨されるアクションを箇条書きで提示してください。

## 関連情報
追加で確認すべき情報があれば記載してください。

注意:
- 断定的な表現は避け、「〜と推測されます」「〜の可能性があります」等の表現を使ってください
- 技術的な専門用語には簡単な説明を添えてください
- 緊急度が高いと判断した場合は冒頭に「⚠️ 緊急度: 高」と明記してください
</instruction>"""

def backlog_get(path):
    """Backlog API GET リクエスト"""

    url = f"https://{BACKLOG_SPACE}.backlog.com{path}?apiKey={BACKLOG_API_KEY}"
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())

def backlog_post_comment(issue_key, content):
    """Backlog 課題にコメントを投稿"""

    url = f"https://{BACKLOG_SPACE}.backlog.com/api/v2/issues/{issue_key}/comments?apiKey={BACKLOG_API_KEY}"

    # 絵文字(非BMP文字)を除去(Backlog API の制約)
    content = re.sub(r'[\U00010000-\U0010FFFF]', '', content)

    data = urllib.parse.urlencode({"content": content}).encode("utf-8")
    req = urllib.request.Request(url, data=data, method="POST")
    req.add_header("Content-Type", "application/x-www-form-urlencoded")

    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())

Step 4: CDK でインフラを構築

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# cdk/backlog_claude_stack.py
from aws_cdk import (
    Stack, Duration, RemovalPolicy,
    aws_lambda as _lambda,
    aws_apigateway as apigw,
    aws_sqs as sqs,
    aws_iam as iam,
    aws_lambda_event_sources as event_sources,
)
from constructs import Construct

class BacklogClaudeStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # SQS キュー
        queue = sqs.Queue(
            self, "BacklogWebhookQueue",
            visibility_timeout=Duration.seconds(300),
            retention_period=Duration.days(1),
        )

        # 受信 Lambda
        webhook_receiver = _lambda.Function(
            self, "WebhookReceiver",
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler="handler.handler",
            code=_lambda.Code.from_asset("webhook_receiver"),
            timeout=Duration.seconds(10),
            environment={
                "SQS_QUEUE_URL": queue.queue_url,
                "BACKLOG_WEBHOOK_TOKEN": "your-secret-token",
                "BOT_USER_ID": "12345",
            },
        )
        queue.grant_send_messages(webhook_receiver)

        # API Gateway
        api = apigw.RestApi(self, "BacklogWebhookAPI")
        webhook_resource = api.root.add_resource("webhook")
        webhook_resource.add_method(
            "POST",
            apigw.LambdaIntegration(webhook_receiver),
        )

        # 処理 Lambda
        agent_invoker = _lambda.Function(
            self, "AgentInvoker",
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler="handler.handler",
            code=_lambda.Code.from_asset("agent_invoker"),
            timeout=Duration.seconds(120),
            memory_size=512,
            environment={
                "BACKLOG_SPACE": "yourspace",
                "BACKLOG_API_KEY": "your-api-key",
                "MODEL_ID": "anthropic.claude-sonnet-4-20250514-v1:0",
            },
        )

        # Bedrock 権限
        agent_invoker.add_to_role_policy(
            iam.PolicyStatement(
                actions=["bedrock:InvokeModel"],
                resources=["arn:aws:bedrock:*::foundation-model/anthropic.*"],
            )
        )

        # SQS トリガー
        agent_invoker.add_event_source(
            event_sources.SqsEventSource(queue, batch_size=1)
        )

Step 5: Backlog 側の Webhook 登録

  1. Backlog のプロジェクト設定 → インテグレーションWebhook を開く
  2. 以下を設定:
項目
Webhook 名Claude 自動分析
URLhttps://{api-id}.execute-api.{region}.amazonaws.com/prod/webhook?token=your-secret-token
通知するイベント✅ 課題の追加
  1. 「実行テスト」で送信を確認

無限ループ防止の設計

Bot が投稿したコメントが再び Webhook をトリガーし、無限にコメントが投稿される事態を防ぐ必要があります。

多層防御

レイヤー 1: イベントタイプ制限
  → type=1(課題追加)のみ処理。コメント追加(type=3)は無視

レイヤー 2: ユーザー ID チェック
  → Bot ユーザーが作成した課題はスキップ

レイヤー 3: メンション制限(拡張時)
  → コメント応答を追加する場合、@claude メンション時のみ処理

課題追加のみをトリガーにする場合、レイヤー 1 だけで十分ですが、将来コメント応答機能を追加する際にはレイヤー 2・3 が重要になります。

拡張パターン

A. コメント応答(@claude メンション)

課題追加時の自動分析に加え、コメントで @claude と書くと応答する機能を追加できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# webhook_receiver に追加
MENTION_PATTERN = "@claude"

if body.get("type") == 3:  # コメント追加
    comment_body = body.get("content", {}).get("comment", {}).get("content", "")
    if MENTION_PATTERN not in comment_body:
        return {"statusCode": 200, "body": "Skipped: no mention"}
    # Bot 自身のコメントはスキップ
    if body.get("createdUser", {}).get("id") == BOT_USER_ID:
        return {"statusCode": 200, "body": "Skipped: bot comment"}

B. Claude Code ヘッドレスモード連携

Backlog 課題の内容をもとに、Claude Code でコードベースを直接分析させることも可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import subprocess

def analyze_with_claude_code(issue_description):
    """Claude Code のヘッドレスモードでコードベースを分析"""

    result = subprocess.run(
        [
            "claude", "-p",
            f"以下の問い合わせに関連するコードを調査してください:\n{issue_description}",
            "--allowedTools", "Read,Grep,Glob",
            "--output-format", "json",
        ],
        capture_output=True, text=True, timeout=120,
    )

    return json.loads(result.stdout)["result"]

この場合、Lambda ではなく ECS タスクや EC2 上で実行する必要があります。

C. 分類結果に応じた自動アクション

Claude の分析結果をパースして、課題の属性を自動更新します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def auto_update_issue(issue_key, analysis):
    """分析結果に基づいて課題を自動更新"""

    params = {}

    # 緊急度が高い場合、優先度を「高」に変更
    if "緊急度: 高" in analysis:
        params["priorityId"] = 2  # 高

    # 障害報告の場合、カテゴリを設定
    if "障害報告" in analysis:
        params["categoryId[]"] = [CATEGORY_BUG_ID]

    if params:
        backlog_patch(f"/api/v2/issues/{issue_key}", params)

発展構成: ルール可変型アーキテクチャ(CLAUDE.md + ECS)

問題: ルール変更のたびに Lambda を再デプロイしたくない

MVP 構成では分析ルールやプロンプトが Lambda のコードに埋め込まれています。しかし運用が進むと:

  • 「障害報告なら GitHub Issue も作って」
  • 「特定顧客からの問い合わせは優先度を自動で上げて」
  • 「新しいカテゴリ分類を追加して」

こうしたルール変更のたびに Lambda コードを修正 → デプロイするのは運用コストが高すぎます。

解決策: ルールを CLAUDE.md に、実行を ECS に分離

変更前(MVP):
  SQS → Lambda → Bedrock API(ルールがコードに埋め込み)→ Backlog API
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        ルール変更 = コード修正 + デプロイ

変更後(ルール可変型):
  SQS → Lambda → ECS Task(Claude Code -p)→ Backlog API / gh CLI
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  ルール変更 = CLAUDE.md を Git push するだけ

ルール(何をするか)実行基盤(どう動かすか) を分離します。

構成図

┌──────────┐  Webhook  ┌──────────┐  SQS  ┌──────────┐  RunTask  ┌────────────────┐
│ Backlog  │ ────────→ │  Lambda  │ ────→ │  Lambda  │ ────────→ │  ECS Fargate   │
│ (課題追加) │           │ (受信)    │       │ (起動)    │           │                │
└──────────┘           └──────────┘       └──────────┘           │  Claude Code   │
                                                                  │  -p "..."      │
┌──────────┐  コメント投稿 / gh issue create                        │                │
│ Backlog  │ ◀──────────────────────────────────────────────────── │  CLAUDE.md     │
│ GitHub   │                                                       │  .claude/rules │
└──────────┘                                                       └────────────────┘
                                                                          ↑
                                                                     Git リポジトリ
                                                                   (ルール定義の管理)

ルール変更時に ECS の再デプロイは必要か?

不要です。 ECS タスクは起動のたびに git clone でルール定義リポジトリの最新版を取得するため、Docker イメージの再ビルドも ECS タスク定義の更新も必要ありません。

ルール変更の運用:
  1. .claude/rules/*.md を編集
  2. Git push
  3. 完了(次に Backlog 課題が来たとき、自動で最新ルールが適用される)

Docker イメージの再ビルドが必要なケース:
  - Claude Code CLI のバージョンアップ
  - gh CLI など新しいツールの追加
  - entrypoint.sh の処理フロー自体を変更する場合

つまり、ルール(何をするか)の変更は Git push だけ、実行環境(どう動かすか)の変更だけが再ビルド対象です。

なぜ Lambda ではなく ECS か

観点LambdaECS Fargate
Claude Code CLI実行不可(バイナリサイズ・実行時間制限)Docker で自由に構成可能
gh CLIレイヤー追加が必要Docker イメージに含めるだけ
実行時間最大 15 分無制限
ファイルシステム/tmp のみ・512MBEFS マウント可能
CLAUDE.md 参照不可Git clone してそのまま参照

ルール定義リポジトリの構造

ルール専用のリポジトリ(または既存リポジトリの一部)を用意します:

backlog-support-agent/
├── CLAUDE.md                    # メインのルール定義
├── .claude/
│   └── rules/
│       ├── classification.md    # 分類ルール
│       ├── github-escalation.md # GitHub Issue エスカレーション条件
│       ├── priority-rules.md    # 優先度自動設定ルール
│       └── response-style.md   # 回答スタイル
├── Dockerfile
└── entrypoint.sh

CLAUDE.md の例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Backlog 問い合わせ自動分析エージェント

## 役割
あなたは Backlog に登録された問い合わせ課題を分析するサポートエージェントです。

## 基本動作
1. 渡された課題情報を分析する
2. 分類・要約・推奨対応を生成する
3. `backlog-cli` でコメントを投稿する
4. 必要に応じて GitHub Issue を作成する

## ツール
- `backlog-cli comment <issue-key> <content>`: Backlog にコメント投稿
- `gh issue create`: GitHub Issue の作成
- `backlog-cli update <issue-key> --priority <id>`: 課題属性の更新

.claude/rules/github-escalation.md の例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# GitHub Issue エスカレーション条件

以下の条件に該当する場合、関連する GitHub リポジトリに Issue を作成すること:

## 障害報告の場合
- リポジトリ: spin-dd/taihei-epm-server
- ラベル: bug, from-backlog
- タイトル: [Backlog #{課題番号}] {課題タイトル}
- 本文に Backlog 課題へのリンクを含める

## 機能要望の場合
- リポジトリ: spin-dd/taihei-epm-issues
- ラベル: enhancement, from-backlog

## エスカレーション不要
- 使い方の質問 → コメント回答のみ
- アカウント関連 → コメント回答のみ

.claude/rules/priority-rules.md の例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 優先度自動設定ルール

## 緊急(priorityId: 2)
- 「ログインできない」「全員」「止まっている」を含む場合
- 報告者が VIP 顧客リストに含まれる場合

## 高(priorityId: 3)
- 「エラー」「不具合」「障害」を含む場合
- 複数ユーザーに影響すると判断される場合

## 中(priorityId: 4)- デフォルト
- 上記に該当しない場合

Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
FROM node:20-slim

# Claude Code CLI のインストール
RUN npm install -g @anthropic-ai/claude-code

# gh CLI のインストール
RUN apt-get update && apt-get install -y curl git jq \
    && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
       | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
    && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
       | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
    && apt-get update && apt-get install -y gh \
    && apt-get clean

# backlog-cli(簡易ラッパー)のインストール
COPY backlog-cli /usr/local/bin/backlog-cli
RUN chmod +x /usr/local/bin/backlog-cli

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
set -euo pipefail

# 環境変数から課題情報を取得
ISSUE_KEY="${ISSUE_KEY}"
ISSUE_JSON="${ISSUE_JSON}"

# ルール定義リポジトリを clone
git clone "${RULES_REPO_URL}" /workspace
cd /workspace

# Claude Code ヘッドレスモードで分析を実行
# CLAUDE.md と .claude/rules/ が自動的に読み込まれる
claude -p "以下の Backlog 課題を分析し、ルールに従って対応してください:

課題キー: ${ISSUE_KEY}
課題情報:
${ISSUE_JSON}

分析結果に基づいて以下を実行してください:
1. backlog-cli でコメントを投稿
2. 必要に応じて gh issue create で GitHub Issue を作成
3. 必要に応じて backlog-cli update で課題属性を更新" \
  --allowedTools "Bash(backlog-cli *),Bash(gh issue *),Bash(gh api *),Read,Grep" \
  --output-format text

処理 Lambda(ECS タスク起動版)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# agent_invoker/handler.py(ECS 版)
import json
import os
import boto3

ecs = boto3.client("ecs")

CLUSTER = os.environ["ECS_CLUSTER"]
TASK_DEFINITION = os.environ["ECS_TASK_DEFINITION"]
SUBNETS = os.environ["SUBNETS"].split(",")
SECURITY_GROUPS = os.environ["SECURITY_GROUPS"].split(",")

def handler(event, context):
    """SQS メッセージから ECS タスクを起動"""

    for record in event["Records"]:
        body = json.loads(record["body"])
        content = body["content"]
        issue_key = f'{body["project"]["projectKey"]}-{content["key_id"]}'

        ecs.run_task(
            cluster=CLUSTER,
            taskDefinition=TASK_DEFINITION,
            launchType="FARGATE",
            networkConfiguration={
                "awsvpcConfiguration": {
                    "subnets": SUBNETS,
                    "securityGroups": SECURITY_GROUPS,
                    "assignPublicIp": "ENABLED",
                }
            },
            overrides={
                "containerOverrides": [{
                    "name": "backlog-agent",
                    "environment": [
                        {"name": "ISSUE_KEY", "value": issue_key},
                        {"name": "ISSUE_JSON", "value": json.dumps(content, ensure_ascii=False)},
                    ],
                }]
            },
        )

    return {"statusCode": 200}

Agent SDK を使う方式(Lambda 内で完結)

ルール変更に S3 を使うことで、ECS を使わず Lambda 内で完結させることも可能です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Agent SDK を Lambda 内で使用する例
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def analyze_with_agent_sdk(issue_json, rules_text):
    """Agent SDK で分析(Lambda 内で実行可能)"""

    prompt = f"""
{rules_text}

---
以下の課題を上記ルールに従って分析してください:
{issue_json}
"""

    options = ClaudeAgentOptions(
        allowed_tools=["Bash(backlog-cli *)", "Bash(gh issue *)"],
        system_prompt="あなたは Backlog サポートエージェントです。",
    )

    result_text = ""
    async for message in query(prompt=prompt, options=options):
        if hasattr(message, "content"):
            result_text += message.content

    return result_text

# S3 からルール定義を取得
def get_rules_from_s3():
    s3 = boto3.client("s3")
    obj = s3.get_object(Bucket="my-rules-bucket", Key="rules.md")
    return obj["Body"].read().decode("utf-8")

ルール変更の運用フロー

運用者がルールを変更したい場合:

1. ルール定義リポジトリの .claude/rules/*.md を編集
2. PR を作成 → レビュー → マージ
3. 完了。次回の課題分析から新ルールが自動適用される

Lambda の再デプロイ:       不要
Docker イメージの再ビルド:  不要(ECS タスク起動時に git clone で最新取得)
ECS タスク定義の更新:      不要

ポイント: ECS タスクは「都度起動・都度破棄」のため、常に最新のルールが適用されます。常駐型(ECS Service)のように定期 git pull を仕込む必要もありません。

方式比較

方式ルール変更再ビルド/再デプロイ外部ツール実行コスト
MVP: Lambda + Bedrockコード修正 + デプロイ必要Backlog API のみ安い($8/月)
ECS Task + Claude Code -pGit push のみ不要gh, backlog-cli 等自由中($15-25/月)
Lambda + Agent SDK + S3S3 にアップロード不要Agent SDK 経由安い($10/月)

推奨: まず MVP で動作確認し、ルール変更頻度が高まったら ECS + Claude Code 方式に移行。

セキュリティ考慮事項

項目対策
Webhook 認証URL にシークレットトークンを含め、Lambda で検証
API キー管理AWS Secrets Manager に格納し、Lambda から参照
IP 制限Backlog のWebhook送信元 IP を API Gateway の WAF で制限
入力サニタイズ課題内容をそのまま実行せず、プロンプトテンプレートに埋め込む
コスト制御Bedrock の呼び出し回数を CloudWatch で監視

コスト試算

月間 500 件の問い合わせ課題を想定:

リソース月額概算
API Gateway$0.01(500 リクエスト)
Lambda(受信)$0.01 未満
Lambda(処理)$0.50(500 回 × 30 秒 × 512MB)
SQS$0.01 未満
Bedrock(Claude Sonnet)$7.50(500 回 × 平均 5K トークン)
合計約 $8/月

まとめ

Backlog Webhook + AWS の構成で、問い合わせ課題の自動分析 → コメント投稿を実現できます。

MVP 構成(まず動かす)

  • Webhook 受信 → SQS → Lambda → Bedrock でシンプルに自動分析
  • 月額約 $8、CDK で IaC 化

ルール可変型構成(運用を回す)

  • CLAUDE.md + .claude/rules/ にルールを記述し、Lambda コードと分離
  • ルール変更 = Git push するだけ。Lambda の再デプロイ不要
  • ECS Fargate + Claude Code ヘッドレスモードgh CLI や任意のツールも実行可能
  • GitHub Issue 自動作成、優先度自動変更などの拡張もルール追記だけで対応

設計の要点

  • Backlog の 10 秒タイムアウト → SQS による非同期化で対応
  • 無限ループ防止 → イベントタイプ制限 + Bot ユーザー ID チェック
  • ルールとコードの分離 → 運用ルールの変更にデプロイを伴わせない

GitHub の self-hosted Action と同じ感覚で、Backlog の課題更新をトリガーに任意の処理を実行する基盤として活用できます。

Claude の認証・料金プラン:どの構成で何が必要か

構成ごとの認証要件

本記事で紹介した 3 つの構成では、それぞれ必要な認証方法が異なります。

構成必要な認証Claude Code サブスクリプション備考
MVP: Lambda + BedrockAWS IAM(Bedrock アクセス権)不要Bedrock の従量課金のみ
ECS + Claude Code -pAnthropic API キー or AWS BedrockAPI キーの場合は必要後述
Lambda + Agent SDKAnthropic API キー or AWS BedrockAPI キーの場合は必要後述

MVP 構成は Claude Code 契約不要

MVP 構成(Lambda + Bedrock)は、Claude Code のサブスクリプションは一切不要です。AWS の Bedrock サービスとして Claude モデルを呼び出すため、必要なのは:

  • AWS アカウント
  • Bedrock で Claude モデルへのアクセスを有効化
  • IAM ロールに bedrock:InvokeModel 権限を付与

これだけで動作します。料金は Bedrock の従量課金(入出力トークン数に応じた課金)のみです。

ECS + Claude Code ヘッドレスモードの認証オプション

Claude Code をヘッドレスモード(claude -p)で実行する場合、以下の認証方法が選択できます:

認証方法環境変数料金体系
Anthropic API キーANTHROPIC_API_KEYAPI 従量課金(console.anthropic.com で取得)
Amazon BedrockCLAUDE_CODE_USE_BEDROCK=1 + AWS 認証情報Bedrock 従量課金
Google Vertex AICLAUDE_CODE_USE_VERTEX=1 + GCP 認証情報Vertex AI 従量課金
1
2
3
4
5
6
7
# Anthropic API キーを使う場合
docker run -e ANTHROPIC_API_KEY=sk-ant-... backlog-agent

# Amazon Bedrock を使う場合(IAM ロールで認証)
docker run -e CLAUDE_CODE_USE_BEDROCK=1 \
           -e AWS_REGION=us-east-1 \
           backlog-agent

推奨: 既に AWS 環境で構築しているなら、Bedrock 経由が最もシンプルです。ECS タスクの IAM ロールに Bedrock 権限を付与するだけで、API キーの管理が不要になります。

個人開発での Claude Code 利用

ローカルで Claude Code を対話的に使う場合は、以下の選択肢があります:

プラン料金用途
Pro プラン$20/月個人利用(一定の使用制限あり)
Max プラン$100/月 or $200/月ヘビーユース向け(制限緩和)
API キー従量課金サーバーサイド・CI/CD 向け

Pro/Max プランは対話的な利用向けで、サーバーサイドの自動化(ECS ヘッドレスモード)には API キーまたは Bedrock 認証が必要です。

まとめ: どのプランを選ぶべきか

「まず試したい」
  → MVP 構成(Lambda + Bedrock)で開始。Claude Code 契約不要。

「ルール可変で本格運用したい」
  → ECS + Claude Code + Bedrock 認証。API キー管理不要で IAM ロールだけで動く。

「ローカル開発で Claude Code を使いたい」
  → Pro プラン($20/月)で十分。API キーは不要。

参考