Slack MCP Agent — エラーハンドリング設計
v1.0 / 2026-04-18
パイプライン各層の失敗パターン、却下再生成、再実行、中止のフローを定義する。
docs/architecture.md Section 5・9・10、docs/data-model.md Section 3 と整合。
1. 設計原則
#
原則
説明
E-1
ユーザーに常にフィードバック
エラー発生時は Slack カードを失敗状態に更新し、何が起きたかを表示
E-2
リカバリ可能にする
失敗 → [再実行]、却下 → 再生成。ユーザーが行き詰まらない設計
E-3
エラーは握りつぶさない
全エラーを audit_logs に記録。Agent にはテキストでエラーを返す
E-4
リトライは自動、リカバリは手動
一時的エラー (429, 5xx) は自動リトライ。業務エラーはユーザー判断
E-5
タイムアウトは明示的
L4 は 360 秒上限。超過は failed として処理
2. エラー分類
flowchart TD
E["エラー発生"] --> T{"一時的?"}
T -->|Yes| R["自動リトライ\n(最大 3 回, backoff)"]
R --> S{"成功?"}
S -->|Yes| OK["正常続行"]
S -->|No| F["失敗処理"]
T -->|No| C{"業務エラー?"}
C -->|Yes| U["ユーザーに判断委譲\n(却下/再実行)"]
C -->|No| F
F --> L["audit_log 記録\nSlack カード更新\nDB ステータス更新"]
分類
例
対処
一時的エラー
API 429/5xx、ネットワーク断、DB ロック
自動リトライ (exponential backoff)
認証エラー
freee 401、Slack トークン無効
トークンリフレッシュ → リトライ (1 回)
業務エラー
LLM 出力パース失敗、不正な JSON
ユーザーに通知、再実行可能
致命的エラー
DB マイグレーション失敗、設定不備
起動時に即失敗、ログ出力
タイムアウト
L4 ポーリング上限到達
failed → [再実行] ボタン
ユーザー起因
却下、中止
正常フローとして処理
3. パイプライン各層のエラーハンドリング
3.1 L1: Message → Task
エラー
検知方法
対処
Slack 表示
Anthropic API エラー
anthropic.APIError
リトライ (3 回) → 失敗
スレッドにエラーメッセージを投稿
JSON パースエラー
json.JSONDecodeError
リトライ (1 回) → 失敗
同上
必須キー欠落
KeyError
リトライ (1 回) → 失敗
同上
レートリミット
anthropic.RateLimitError
backoff リトライ → 失敗
同上
async def run_l1_with_retry ( client , user_message , model , max_retries = 3 ):
for attempt in range ( max_retries ):
try :
result = await run_l1 ( client , user_message , model )
_validate_task_json ( result )
return result
except ( anthropic . RateLimitError , anthropic . InternalServerError ):
if attempt == max_retries - 1 :
raise
await asyncio . sleep ( 2 ** attempt )
except json . JSONDecodeError :
if attempt == max_retries - 1 :
raise
3.2 L2: Task → Prompt
エラー
対処
Anthropic API エラー
L1 と同様のリトライ
空レスポンス
リトライ (1 回) → 失敗 → Prompt カードを失敗表示
L2 の出力は自然言語テキストのため、パースエラーは発生しにくい。
3.3 L3: Prompt → Process
エラー
検知方法
対処
JSON パースエラー
json.JSONDecodeError
マークダウンフェンス除去後にリトライ
steps キー欠落
KeyError
リトライ → 失敗
不正な tool 名
バリデーション
エラーを含めてリトライ → 失敗
steps が空配列
バリデーション
リトライ → 失敗
def _validate_steps ( steps : list [ dict ], valid_tools : set [ str ]) -> None :
if not steps :
raise ValueError ( "steps array is empty" )
for step in steps :
required = { "stepId" , "order" , "tool" , "toolInput" }
missing = required - set ( step . keys ())
if missing :
raise ValueError ( f "Step { step . get ( 'stepId' , '?' ) } missing keys: { missing } " )
if step [ "tool" ] not in valid_tools and step [ "tool" ] != "agent_decide" :
raise ValueError ( f "Unknown tool: { step [ 'tool' ] } " )
3.4 L4: Managed Agent Session
エラー
検知方法
対処
Slack 表示
Session 作成失敗
anthropic.APIError
リトライ (2 回) → failed
Execution カード: 失敗 + [再実行]
ポーリングタイムアウト
MAX_POLL_ATTEMPTS 到達
failed
同上
Session エラーステータス
session.status == "error"
failed
同上
ツール実行エラー
ツール結果にエラー
Agent に返却、Agent が判断
Execution カード: 進捗更新
MCP Server 到達不可
Agent イベントにエラー
failed
Execution カード: 失敗
ngrok 切断
MCP 接続タイムアウト
failed
同上
4. 却下 → 再生成フロー
4.1 Prompt 却下
sequenceDiagram
actor User
participant Slack
participant Backend
participant DB
participant LLM as Anthropic API
User->>Slack: [却下] クリック
Slack->>Backend: action: reject_prompt
Backend->>Slack: views.open (理由入力モーダル)
User->>Slack: 理由を入力して送信
Slack->>Backend: view_submission
Backend->>DB: prompts UPDATE (status: rejected, rejection_reason)
Backend->>DB: audit_logs INSERT (prompt.rejected)
Backend->>LLM: L2 再実行 (前 Prompt + 却下理由)
LLM-->>Backend: 新 Prompt テキスト
Backend->>DB: prompts INSERT (version +1, status: generating)
Backend->>DB: prompts UPDATE (status: pending_approval)
Backend->>Slack: chat.update (Prompt カード: 新バージョン承認待ち)
4.2 Process 却下
Prompt 却下と同一パターン。L3 を再実行し、新バージョンの Process を生成。
4.3 再生成時のガードレール
ルール
説明
バージョン上限
最大 5 回再生成。超過時は「最大試行回数に達しました」と表示
却下理由必須
空文字の却下理由は受け付けない (モーダルでバリデーション)
前バージョン参照
再生成プロンプトに前バージョンの内容 + 却下理由を必ず含める
5. 再実行フロー
5.1 失敗後の再実行
sequenceDiagram
actor User
participant Slack
participant Backend
participant DB
participant Agent as Managed Agent
Note over Slack: Execution カード: 失敗 + [再実行] ボタン
User->>Slack: [再実行] クリック
Slack->>Backend: action: retry_execution
Backend->>DB: executions INSERT (新レコード, status: pending)
Backend->>DB: tasks UPDATE (status: running)
Backend->>DB: audit_logs INSERT (execution.started)
Backend->>Slack: chat.update (Execution カード: 実行中)
Backend->>Agent: sessions.create + events.send(steps)
loop ポーリング (3 秒間隔)
Backend->>Agent: sessions.retrieve
Agent-->>Backend: status + events
Backend->>Slack: chat.update (進捗更新)
end
Agent-->>Backend: status: idle (完了)
Backend->>DB: executions UPDATE (status: completed)
Backend->>DB: tasks UPDATE (status: completed)
Backend->>DB: audit_logs INSERT (execution.completed)
Backend->>Slack: chat.update (Execution カード: 完了)
5.2 再実行のルール
ルール
説明
同一 Process
再実行は同じ承認済み Process steps を使用
新 Execution
新しい Execution レコードを作成 (履歴保持)
新 Session
Managed Agent Session は新規作成
再実行上限
最大 3 回。超過時はメッセージ表示
6. 中止フロー
6.1 実行中の中止
sequenceDiagram
actor User
participant Slack
participant Backend
participant DB
participant Agent as Managed Agent
Note over Slack: Execution カード: 実行中 + [中止] ボタン
User->>Slack: [中止] クリック
Slack->>Backend: action: cancel_execution
Backend->>Agent: sessions.cancel(session_id)
Backend->>DB: executions UPDATE (status: cancelled, cancelled_by, cancelled_at)
Backend->>DB: tasks UPDATE (status: cancelled)
Backend->>DB: audit_logs INSERT (execution.cancelled)
Backend->>Slack: chat.update (Execution カード: 中止)
6.2 中止のルール
ルール
説明
実行中のみ
running ステータスの Execution のみ中止可能
即時反映
ボタンクリック後、カードを即座に「中止中...」に更新
Session 停止
sessions.cancel が失敗しても DB は cancelled に更新
中止後の再実行
中止後は新しい Execution で再実行可能
7. 外部 API エラーハンドリング
7.1 freee API
HTTP
エラー名
対処
リトライ
401
Unauthorized
トークンリフレッシュ → リトライ
1 回
403
Forbidden
スコープ不足。エラーテキストを Agent に返す
しない
404
Not Found
従業員/レコード不存在。エラーテキストを Agent に返す
しない
429
Rate Limited
Retry-After ヘッダに従い待機
最大 3 回
500-599
Server Error
Exponential backoff
最大 3 回
async def call_freee_api ( method , url , token_manager , tenant_id , conn , ** kwargs ):
for attempt in range ( 4 ):
token = await token_manager . get_access_token ( tenant_id , conn )
headers = { "Authorization" : f "Bearer { token } " }
resp = await httpx . request ( method , url , headers = headers , ** kwargs )
if resp . status_code == 401 and attempt == 0 :
await token_manager . force_refresh ( tenant_id , conn )
continue
if resp . status_code == 429 :
wait = int ( resp . headers . get ( "Retry-After" , 2 ** attempt ))
await asyncio . sleep ( wait )
continue
if resp . status_code >= 500 :
await asyncio . sleep ( 2 ** attempt )
continue
resp . raise_for_status ()
return resp . json ()
raise FreeeAPIError ( f "freee API failed after retries: { url } " , resp . status_code )
7.2 Anthropic API
エラー
対処
リトライ
RateLimitError
Exponential backoff
最大 3 回
InternalServerError
Exponential backoff
最大 3 回
AuthenticationError
起動時にフェイル、API Key 確認
しない
BadRequestError
プロンプト修正が必要。ログ記録
しない
APITimeoutError
リトライ
最大 2 回
7.3 Slack API
エラー
対処
slack_api_error
ログ記録。ユーザーへの通知はベストエフォート
Rate Limited
Bolt が自動リトライ
Socket Mode 切断
Bolt が自動再接続
8. DB エラーハンドリング
エラー
対処
OperationalError (ロック)
WAL モード + PRAGMA busy_timeout=5000 で軽減
IntegrityError (FK 違反)
バグ。ログ記録 + 500
IntegrityError (UNIQUE 違反)
冪等性チェック (既存レコード返却)
マイグレーション失敗
起動時に即失敗。手動対応
9. ユーザー向けエラーメッセージ
9.1 Slack エラーメッセージ
全エラーメッセージは日本語で、技術詳細は含めない。
シーン
メッセージ
L1 失敗
「タスクの解析に失敗しました。もう一度お試しください。」
L2 失敗
「実行方針の生成に失敗しました。もう一度お試しください。」
L3 失敗
「実行ステップの生成に失敗しました。もう一度お試しください。」
L4 タイムアウト
「実行がタイムアウトしました。[再実行] で再試行できます。」
L4 エラー
「実行中にエラーが発生しました。[再実行] で再試行できます。」
freee 接続エラー
「freee への接続に失敗しました。管理画面で接続状態を確認してください。」
再生成上限
「最大試行回数(5 回)に達しました。タスクを新しく作成してください。」
再実行上限
「最大再実行回数(3 回)に達しました。新しいタスクを作成してください。」
9.2 Web 管理画面エラー
シーン
表示
DB 接続エラー
error.tsx: 「データベースに接続できません」
認証失敗
/login にリダイレクト
404
not-found.tsx: 「ページが見つかりません」
設定保存失敗
Toast: 「設定の保存に失敗しました」
OAuth 失敗
Toast: 「freee の認証に失敗しました」
10. ログ戦略
10.1 構造化ログ
import logging
import json
logger = logging . getLogger ( "slack-mcp-agent" )
def log_error ( layer : str , error : Exception , context : dict ):
logger . error ( json . dumps ({
"layer" : layer ,
"error_type" : type ( error ) . __name__ ,
"error_message" : str ( error ),
** context ,
}, ensure_ascii = False ))
10.2 ログレベル
レベル
用途
ERROR
API エラー、パースエラー、DB エラー
WARNING
リトライ発生、タイムアウト接近
INFO
パイプライン進行 (L1 完了, 承認, 実行開始 等)
DEBUG
API リクエスト/レスポンス詳細
変更履歴
日付
バージョン
変更内容
2026-04-18
v1.0
初版作成