
【Claude Code中級編 #10】StopFailureとPermissionDenied——エラー・拒否への対処
Claude Code Hooksには7つのイベントがある。そのうち「エラーや拒否に対処する」専用のイベントが2つある——StopFailure と PermissionDenied。
この記事でわかること:
Stop/StopFailure/PermissionDeniedの3つの違いStopFailureのinputデータの形式と使い方(実測済み)PermissionDeniedが発火する条件とauto modeの仕組み
3つのStop系イベントの違い
| イベント | 発火タイミング | 用途 |
|---|---|---|
Stop |
Claudeが正常に応答を終了したとき | サマリー生成・完了通知 |
StopFailure |
APIエラーでターンが終了したとき | エラーログ・Slack通知・リトライ起動 |
PermissionDenied |
auto mode分類器がツールを拒否したとき | 拒否の記録・retry制御 |
判断基準:
正常終了後に何かしたい → Stop
APIエラー時に自動で対処したい → StopFailure
auto modeの拒否に割り込みたい → PermissionDenied
StopFailure——APIエラー時に自動で対処する
StopFailure は Stop と対になるイベント。Claudeが正常に応答を返せずAPIエラーでターンが終了したとき発火する。
inputに含まれるデータ(実測値)
{
"session_id": "fbcff89f-d402-4f8a-81c0-5b5e527a2916",
"transcript_path": "/Users/yourname/.claude/projects/.../session.jsonl",
"cwd": "/path/to/project",
"hook_event_name": "StopFailure",
"error": "invalid_request",
"last_assistant_message": "There's an issue with the selected model..."
}
Stop との差分:
| フィールド | Stop | StopFailure |
|---|---|---|
hook_event_name |
"Stop" |
"StopFailure" |
error |
なし | あり(エラー種別) |
stop_hook_active |
false |
なし |
last_assistant_message |
あり | あり |
error フィールドに入る値は "invalid_request" や "api_error" など、APIが返したエラー種別。
典型的な用途
① エラーログを残す
#!/usr/bin/env python3
import json, sys, os
from datetime import datetime
input_data = json.loads(sys.stdin.read())
error = input_data.get("error", "unknown")
message = input_data.get("last_assistant_message", "")
cwd = input_data.get("cwd", "")
log_path = os.path.expanduser("~/.claude/stop_failure.log")
with open(log_path, "a") as f:
f.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} error={error} cwd={cwd}\n")
if message:
f.write(f" last_message: {message[:100]}\n")
② Slack通知を送る
#!/usr/bin/env python3
import json, sys, os, urllib.request
input_data = json.loads(sys.stdin.read())
error = input_data.get("error", "unknown")
cwd = input_data.get("cwd", "")
webhook_url = os.environ.get("SLACK_WEBHOOK_URL", "")
if webhook_url:
payload = json.dumps({
"text": f":warning: Claude Code APIエラー\nerror: `{error}`\ncwd: `{cwd}`"
}).encode()
req = urllib.request.Request(webhook_url, data=payload,
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req)
settings.jsonの設定
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/on_stop.py"
}
]
}
],
"StopFailure": [
{
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/on_stop_failure.py"
}
]
}
]
}
}
Stop と StopFailure は別イベントなので、両方設定しても干渉しない。
PermissionDenied——auto mode分類器の拒否に割り込む
PermissionDenied は --permission-mode auto(auto mode)専用のフック(v2.1.88で追加)。
auto modeとは
Claude Codeをエージェント・CI/CDパイプラインなどで非対話的に動かすとき、ツール実行のたびにユーザーへの確認ダイアログが出ると処理が止まる。--permission-mode auto を指定すると、auto mode分類器がツール実行の可否を自動で判定する。
# auto modeで実行
claude -p "リリース手順を実行して" --permission-mode auto
分類器は各ツール呼び出しを評価し、allow(自動承認)・soft_deny(確認が必要)・実行拒否のいずれかを返す。
PermissionDeniedが発火するとき
Claudeがツール呼び出しを試みる
↓
auto mode分類器が評価
↓
分類器が拒否 → PermissionDeniedフックが発火
↓
フックが処理(ログ記録・条件判定など)
↓
retry: true を返すとClaudeが再試行できる
retry: true は PermissionDenied 固有の返り値。フックがこれを返すと、Claudeが別の方法でアプローチし直す。
スクリプト例
inputデータの詳細構造はauto modeが有効な環境での確認が必要。まずはログ記録から始めるのが安全。
#!/usr/bin/env python3
import json, sys, os
from datetime import datetime
input_data = json.loads(sys.stdin.read())
# inputの内容をすべてログに記録して構造を確認する
log_path = os.path.expanduser("~/.claude/permission_denied.log")
with open(log_path, "a") as f:
f.write(f"{datetime.now():%Y-%m-%d %H:%M:%S}\n")
f.write(json.dumps(input_data, ensure_ascii=False, indent=2))
f.write("\n\n")
# retry: true を返すとClaudeが再試行する(条件はinput確認後に実装)
# print(json.dumps({"retry": True}))
sys.exit(0)
注意:auto modeのアカウント要件
--permission-mode auto はすべての環境で有効ではない。実際の動作確認では、tengu_auto_mode_config.enabled === "disabled" というcircuit breakerによって無効化されていることが確認された。
auto mode disabled: tengu_auto_mode_config.enabled === "disabled" (circuit breaker)
canEnterAuto=false
auto modeが有効でない環境では、PermissionDenied フックは発火しない。claude auto-mode config コマンドで現在の設定を確認できる。
claude auto-mode config # 現在の設定を確認
claude auto-mode defaults # デフォルトルールを確認
まとめ
| やりたいこと | 使うもの |
|---|---|
| APIエラーをSlackに通知する | StopFailure + Webhook呼び出し |
| エラー発生をログに残す | StopFailure + ファイル書き込み |
| 正常終了とエラー終了を使い分ける | Stop + StopFailure を別々に設定 |
| auto modeの拒否をハンドリングする | PermissionDenied + retry: true |
設計の原則:
StopFailureはStopの対になるイベント——正常終了とエラー終了で処理を分けられるStopFailureのinputにはerrorフィールドにエラー種別が入るPermissionDeniedは--permission-mode autoが有効な環境でのみ発火するretry: trueはPermissionDenied専用の返り値——Claudeに再試行させたいときに使う
関連記事
- Claude Code Hooks 完全ガイド——全イベント・条件設定・ブロック設計を組み合わせる
- 中級編 #2:Hooksとは何か——Claude Codeの動作に割り込む
- 中級編 #8:CwdChanged——ディレクトリ移動で環境変数を自動切り替える
- 中級編 #9:Hook設定を最適化する——ifフィールドでgit操作・危険コマンドだけ反応させる
← 第9回:Hook設定を最適化する——ifフィールドでgit操作・危険コマンドだけ反応させる | 第11回:コンテキスト管理の技術——トークンを無駄にしない →