
BacklogのWebhookをClaude Code Routines APIにつないで課題の一時捌きをしてみた
こんにちは、せーのです。
Claude Code の Routines は cron や GitHub の PR / Release から回せるようになりましたが、Backlog で課題が追加された瞬間に Claude Code を起動したい、というニーズは公式トリガーだけでは埋まりません。2026年4月に追加された API トリガー(/fire)なら、任意の HTTP イベントから Routine を起動できます。
今日は Backlog Webhook → 薄いアダプタ(Lambda)→ Claude Code Routines /fire という構成を、2026-07-13 時点の公式情報に沿って整理し、手元で試した流れを共有します。細部は Routines ドキュメント と /fire API を正として確認してください。
やりたかったこと
私は Backlog をチケット管理に使っています。バグが起票されたら、関連リポジトリを調査して原因仮説や draft PR まで持っていきたい、という場面は多いです。
これまで n8n の AI Agent ノード + MCP で似たことをやろうとすると、Plan → Execute → Verify のループやツール配線を 自前で組む 必要がありました。一方 Claude Code Routines は、Anthropic 管理クラウド上で フルエージェントセッション として動きます。todo 追跡、実行→観察→再計画、コーディング特化ツールの使い分けなど、ハーネス部分は Claude Code 側に任せられる のが利点です。
さらに課金面でも、Routines は サブスクリプション枠(おさいふ1) を消費します。n8n + API 直叩きだと従量課金になりがちなので、Max プランで日次 run cap(15回/日)の範囲に収まる運用なら、コスト面でもメリットがあります。
Slack の特定文言を拾って同じ /fire を叩く、というパターンも同じ薄いアダプタで実現できます。本稿ではエンジニア作業の見え方がわかりやすい Backlog を主軸 に書きます。
Claude Code Routines とは(おさらい)
Routine は「保存済みの Claude Code 設定」です。プロンプト、対象リポジトリ、コネクタをパッケージ化し、Anthropic 管理クラウドで自動実行します。Research Preview なので仕様変更の可能性はあります。
トリガー地図
| トリガー | 内容 | 備考 |
|---|---|---|
| Scheduled | hourly / daily / weekdays / weekly 等 | 最小間隔 1 時間 |
| API | POST .../routines/{id}/fire + per-routine bearer |
CLI ではトークン発行不可(Web UI のみ) |
| GitHub event | Pull request / Release | Issue / push / discussion は公式表になし |
GitHub Issue、Slack、Backlog の ネイティブトリガーは現状ありません。Anthropic 公式ブログでも「Webhook ソースの拡張を予定」と書かれており、現時点では API トリガーで代替 するのが実務的です。
運用上の特性(押さえておきたい点)
- 個人アカウントに帰属し、実行は個人の daily allowance を消費
- permission picker なし。コネクタ書き込みも承認なしで走る
- プロンプトは 自己完結 であること。成功条件を明示する
- 日次 run cap: Pro 5 / Max 15 / Team・Enterprise 25(Extra usage 有効時は超過をメーター課金)
なぜ「薄いアダプタ」が必要か
Backlog Webhook は指定 URL へ JSON を HTTP POST するだけ です。カスタム Authorization ヘッダや anthropic-beta ヘッダは 設定できません。

したがって Backlog → Anthropic /fire を直接結線することは不可能 です。
ハーネスとコストの比較
| n8n AI ノード + MCP | Routine /fire |
|
|---|---|---|
| 実行単位 | ノード単位の LLM 呼び出し | フル Claude Code セッション |
| ツール | ワークフローで配線した MCP | セッション内で自律選択・再計画 |
| レスポンス | 数秒〜十数秒(同期向き) | /fire は即レス、実作業は非同期 |
| コスト | API 従量 | サブスク枠 + 日次 run cap |
違いを整理すると、n8n は「手順固定のルーティング・分類・通知」、Routine は「探索的なコード調査・draft PR」、というイメージです。定型処理は n8n、重いコード作業だけ Routine、というハイブリッドも実務的です。
やってみた
前提
- Claude Max プラン(Claude Code on the web 有効)
- 対象 GitHub リポジトリ: 個人用 Obsidian Vault(Claude Code 接続済み)
- AWS アカウントで Lambda + Function URL を作成可能
- Backlog プロジェクト管理者権限
Step 1: Routine を作る(API トリガー)
- claude.ai/code/routines → New routine
- Name:
Backlog issue triage - 対象リポジトリを選択
- Environment: Default(Trusted network)
- Connectors: 不要なものは外す
プロンプトはバグ調査用に設定しました。API /fire 向けには、後述の 入力(API トリガー) 段落を足す必要がありました(Step 4 で後から判明)。
あなたは Backlog から起票されたバグ調査エージェントです。
## 入力(API トリガー / Backlog Webhook 経由)
この Routine は API `/fire` で起動します。セッション冒頭に届く
「Backlog課題イベントを受信しました。」で始まるテキストブロックに
課題キー・件名・本文が含まれます。そこから抜き出して調査してください。
## 手順
1. text から課題キー・件名・再現手順・エラーメッセージを抜き出す
2. このリポジトリを調査し、関連しそうなファイル・直近コミットを特定する
3. 原因仮説を最大3つに絞り、それぞれ根拠となるファイルパスと行の根拠を示す
4. 修正が小さく安全なら `claude/` 接頭辞のブランチに最小修正を実装し、テストまたは静的確認を行う
5. 修正できない/危険な場合はコード変更せず、調査レポートだけ残す
## 成功条件
- 調査結果がセッションに残っている
- コードを変えた場合は変更差分がレビュー可能な状態
- 推測と事実を分けて書く
- Backlog や外部サービスへの破壊的書き込みはしない
## やってはいけないこと
- 本番設定の変更
- 無関係なリファクタ
- 依存関係のメジャーアップデート
Select a trigger → API を追加。Save 後に Edit で URL と token が表示される場合があります。URL は .../routines/trig_.../fire まで 末尾までフルコピー してください。
curl でスモークテスト:
export ROUTINE_FIRE_URL='https://api.anthropic.com/v1/claude_code/routines/trig_01XXXX/fire'
export ROUTINE_TOKEN='sk-ant-oat01-XXXX'
curl -sS -X POST "$ROUTINE_FIRE_URL" \
-H "Authorization: Bearer $ROUTINE_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: experimental-cc-routine-2026-04-01" \
-H "Content-Type: application/json" \
-d '{"text": "スモークテスト: READMEを1行要約して終了してください。"}'
結果: 最初 /fire なしの URL で 404 page not found。/fire 付きに修正後、セッションが起動し README 要約まで完了。
README.mdの1行要約:
せーのの個人用Obsidian Vault(業務メモ・技術ドキュメント・ブログネタ管理)のセットアップ手順、
ディレクトリ構成、主要スキル、セキュリティ方針をまとめたガイド。
観察メモ: /fire はセッション作成時点で 200 を返す非同期 API です。完了待ちはありません。
Step 2: 薄いアダプタ(Lambda Function URL)
アダプタは LLM を呼びません。Backlog JSON を受信し、text を散文に整形して /fire に POST するだけです。
環境変数:
| 名前 | 内容 |
|---|---|
ROUTINE_FIRE_URL |
UI でコピーした /fire URL |
ROUTINE_TOKEN |
sk-ant-oat01-... |
ALLOWED_TYPES |
既定 1(課題追加)。コメントも見るなら 1,3 |
WEBHOOK_SHARED_SECRET |
(推奨)クエリパラメータ検証用 |
Lambda ハンドラ(Python 3.12):
import json
import os
import urllib.error
import urllib.request
FIRE_URL = os.environ["ROUTINE_FIRE_URL"]
TOKEN = os.environ["ROUTINE_TOKEN"]
ALLOWED_TYPES = {
int(x.strip())
for x in os.environ.get("ALLOWED_TYPES", "1").split(",")
if x.strip()
}
def build_text(payload: dict) -> str:
project = payload.get("project") or {}
content = payload.get("content") or {}
user = payload.get("createdUser") or {}
project_key = project.get("projectKey") or "UNKNOWN"
key_id = content.get("key_id")
issue_key = f"{project_key}-{key_id}" if key_id is not None else "(unknown)"
issue_type = (content.get("issueType") or {}).get("name") or "(unknown)"
summary = content.get("summary") or "(no summary)"
description = content.get("description") or "(no description)"
created_by = user.get("name") or "(unknown)"
return (
f"Backlog課題イベントを受信しました。\n"
f"activity_id: {payload.get('id')}\n"
f"type: {payload.get('type')}\n"
f"プロジェクト: {project_key} ({project.get('name')})\n"
f"課題キー: {issue_key}\n"
f"件名: {summary}\n"
f"種別: {issue_type}\n"
f"起票者: {created_by}\n"
f"本文:\n{description}\n"
)
def fire_routine(text: str) -> dict:
body = json.dumps({"text": text[:65000]}).encode("utf-8")
req = urllib.request.Request(
FIRE_URL,
data=body,
method="POST",
headers={
"Authorization": f"Bearer {TOKEN}",
"anthropic-version": "2023-06-01",
"anthropic-beta": "experimental-cc-routine-2026-04-01",
"Content-Type": "application/json",
},
)
with urllib.request.urlopen(req, timeout=20) as resp:
return json.loads(resp.read().decode("utf-8"))
def lambda_handler(event, context):
qs = event.get("queryStringParameters") or {}
if qs.get("token") != os.environ.get("WEBHOOK_SHARED_SECRET"):
return {"statusCode": 401, "body": "unauthorized"}
try:
raw = event.get("body") or "{}"
if event.get("isBase64Encoded"):
import base64
raw = base64.b64decode(raw).decode("utf-8")
payload = json.loads(raw)
except (TypeError, ValueError, json.JSONDecodeError):
return {"statusCode": 400, "body": "invalid json"}
activity_type = payload.get("type")
if activity_type not in ALLOWED_TYPES:
return {
"statusCode": 200,
"body": json.dumps({"skipped": True, "type": activity_type}),
}
text = build_text(payload)
try:
result = fire_routine(text)
except urllib.error.HTTPError as e:
err_body = e.read().decode("utf-8", errors="replace")
return {
"statusCode": 502,
"body": json.dumps({"error": err_body, "status": e.code}),
}
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(
{
"ok": True,
"session_id": result.get("claude_code_session_id"),
"session_url": result.get("claude_code_session_url"),
}
),
}
Function URL の設定:
- Lambda 作成(Runtime Python 3.12)
- Configuration → Function URL → Create(Auth: NONE)
- タイムアウト 30 秒程度
- Webhook URL 例(
?token=に共有シークレットを付与):
https://xxxxxx.lambda-url.ap-northeast-1.on.aws/?token=YOUR_SHARED_SECRET
Lambda 直 POST の結果:
{
"ok": true,
"session_id": "session_01YFERE4c3rFjjei9uhCCBiJ",
"session_url": "https://claude.ai/code/session_01..."
}
観察メモ:
type=2(課題更新)はALLOWED_TYPES=1によりskipped: trueで正しくフィルタされた- Function URL は
lambda:InvokeFunctionUrlに加えlambda:InvokeFunctionの resource policy が必要な場合がある(403 Forbidden 対策)
Step 3: Backlog Webhook 設定
CM_BACKLOGTEST_MARKDOWN(Backlogテスト(Markdown))プロジェクトで設定しました。
- プロジェクト設定 → インテグレーション → Webhook → 追加
- Webhook 名例:
routines api test - Webhook URL: Lambda Function URL +
?token=... - 通知イベント: 課題の追加 のみ
- 保存 → 実行テスト(イベント: 課題の追加)

Step 4: E2E 確認
Backlog 実行テストの payload(test issue / test description)で Routine が起動しました。

Routine 詳細ページの 実行 セクションに、13:48・13:50 など API バッジ付きの Run が並びます(UI 上は「Runs タブ」ではなく詳細ページ下部の実行一覧)。
プロンプトと /fire text のズレ(1回目):
初回は「実行時コンテキスト(text)に課題が渡される」前提のプロンプトのため、「課題内容が渡されていない」と判断されました。API トリガー向けの入力説明を足したところ、2回目以降は text ブロックから課題を読み取れました。
2回目以降の調査結果:

- 課題
CM_BACKLOGTEST_MARKDOWN-100「test issue」を確認 - 件名・本文がプレースホルダー的なテスト起票と判断(Webhook 疎通確認目的)
scripts/routines/backlog_fetch.sh等を確認したが、この課題に対応する障害の手がかりなし- 再現手順・エラー情報がなく コード変更は不要 と結論。調査記録のみで完了
結論: Backlog Webhook → Lambda → /fire → Claude Code セッションまで E2E 成功。Run の緑/Backlog 200 はインフラ成功であり、今回のテスト課題では「調査記録のみで完了」が正しい挙動でした。
よくある失敗
| 症状 | 原因候補 |
|---|---|
curl で 404 page not found |
/fire なしの URL |
invalid routine ID |
trig_... ID の末尾欠落(UI からフルコピー) |
| Backlog 送信履歴 502 | Lambda 内 ROUTINE_TOKEN 無効。再発行後に環境変数未更新 |
| Backlog 送信履歴 401 | Function URL の ?token= 不一致 |
| 400 invalid_request | anthropic-beta 欠落、Routine paused、text 過長 |
| 401(Anthropic 直叩き) | トークン不一致・再発行後の旧トークン |
| 429 | 日次 run cap または usage 上限 |
| Backlog 200 だが Routine が「課題未受信」 | プロンプトが API /fire の text の見え方とズレている |
| Backlog 200 だが動かない | type フィルタで skip、または URL 誤り |
| 二重実行 | Webhook リトライ + idempotency なし |
運用のガードレール
- 発火条件を絞る — 全課題ではなく Bug 種別 / 特定カテゴリ / 件名プレフィックス
- 冪等 —
payload["id"]を DynamoDB に Put(条件付き)してから/fire - 日次 budget — Max 15 回を前提に、朝会トリアージ級の頻度に抑える
- プロンプトの成功条件と禁止事項を必ず書く。API トリガー時は
textブロックの読み方も明示する - コネクタ最小権限 — 書き込み不要なら外す
- token 再発行時は Lambda 環境変数も更新 — 更新漏れで Backlog 502 になる
- 結果の人間レビュー — run の緑ステータスはインフラ成功であり、タスク成功ではない
発展パターン
Slack 起点
同じ Lambda(または別 Function)を Slack Workflow の HTTP ステップから呼びます。メッセージ本文を text に載せれば、Backlog と同型です。
AI なし n8n 最小構成
既存 n8n がある場合:
Backlog Trigger / Webhook
→ IF(type=1)
→ HTTP Request(/fire + 必須ヘッダ)
AI Agent ノードは使いません。ワークフロー JSON はリポジトリ内 backlog-webhook-claude-routines/n8n-backlog-routines-fire.json に同梱しています(n8n の Import from File で取り込み)。インポート後、以下を差し替えてください。
- Fire Claude Code Routine ノードの URL → Routine の
/fireURL - httpHeaderAuth Credential を新規作成(Header:
Authorization、Value:Bearer sk-ant-oat01-...) - Backlog Webhook ノードの Production URL を Backlog に登録
ハイブリッド
Backlog → n8n(分類・優先度・通知)
└─ 優先度 High かつ Bug のときだけ /fire
精度・コスト・レイテンシのバランスが最も実務的です。
まとめ
- Backlog Webhook はカスタムヘッダを付けられないため、薄いアダプタ(Lambda 等)が必要
- アダプタは LLM を呼ばず、Backlog JSON を散文
textに整形して/fireに POST するだけ - n8n バイパス = AI Agent / MCP ハーネスを捨て、Claude Code 側の agentic ループに処理本体を寄せる
- 課金は サブスク枠(おさいふ1) + 日次 run cap。テスト用プロジェクトで絞って検証するのが安全
/fireの URL は.../fireまで、Routine ID は UI から フルコピー- token 再発行後は Lambda 環境変数も更新(Backlog 502 の典型原因)
- API トリガー向けプロンプトで
textの読み方を明示 しないと「課題未受信」と誤判断される - Backlog 200 / Run 緑 = インフラ成功。タスク成功はセッション内容で人間確認
/fireに idempotency はない。Webhook リトライ対策はアダプタ側で自前管理








