Claude Codeのprompt hookでStopを判断させる — ok・reason・impossible
type: promptのフックはモデルにok/reason/impossibleのJSONを返させて判断します。Stopでの継続と停止、PreToolUse・PostToolUseのcontinueOnBlockの違いを比べます。
type: "prompt" のフックは、シェルコマンドの代わりにモデルへ判断を任せるフックです。Claude Codeがプロンプトとフックの入力JSONをモデルに送り、モデルが ok・reason・impossible を含むJSONで答えます。Stopで「まだ終わっていない」と差し戻すときも、「この条件は満たせない」と止めを許すときも、この3つのフィールドで決まります。
一方、PreToolUseとPostToolUseでは ok: false の既定の挙動がStopと違います。ターンが終わるか続くかを分けるのが continueOnBlock です。ここではイベントごとの分岐を表にして、設定例とつまずきやすい点をまとめます。
prompt hookとは — モデルがJSONで判断を返すフック
prompt hookは、次の流れで動きます。
- フックの入力JSONと
promptを、Claudeのモデルに送る(既定はバックグラウンド処理に使われるモデル) - モデルが判断をJSONで返す
- Claude Codeがその判断を自動で処理する
決定的なルールで書ける判定(ファイル名の一致、コマンド文字列の照合など)はcommandフックが向きます。「タスクは本当に終わっているか」のように判断が要る場面で、prompt hookを使います。
Stopに置く最小構成
Stopで完了を確認する最小の設定は、次の形です(公式ガイドの例に沿った形)。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}モデルが "ok": false を返すと、reason がClaudeへの次の指示として戻り、Claudeは作業を続けます。"ok": true なら停止が許されます。設定は .claude/settings.json などの通常のフック設定場所に置きます。
prompt に $ARGUMENTS を書くと、その位置にフックの入力JSONが差し込まれます。書かなかった場合は、入力JSONがプロンプトの末尾に追加されます。
返すJSONの3つのフィールド
モデルに返させるJSONは次の形です。
{ "ok": true, "reason": "判断の理由", "impossible": false }| フィールド | 意味 |
|---|---|
ok | 意味true で許可。false の動きはイベントごとに異なる |
reason | 意味ok が false のときは必須 |
impossible | 意味ok: false と一緒に返す任意項目。条件が永久に満たせないとモデルが判断したときに立てる |
impossible が効くのはStopとSubagentStopだけです。ほかのイベントと、agent型のフックでは無視されます。
impossible: trueで停止を許す
Stopの ok: false は「理由を渡して続けさせる」動作です。条件が原理的に満たせない場合、これは無駄な往復になります。たとえば「本番環境でE2Eテストが通ること」を確認させているのに、そもそも本番に接続できないセッションでは、何度差し戻しても結果は変わりません。
モデルが "impossible": true を付けて ok: false を返すと、Claude Codeは reason を差し戻さずに停止を許し、ターンが終わります。プロンプト側で、満たせない場合の返し方を指定しておく形です。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check whether the requested change is implemented and the tests were run. Input: $ARGUMENTS\n\nIf work remains, respond with {\"ok\": false, \"reason\": \"<what to do next>\"}. If the remaining work cannot be done in this session (for example, a required credential is missing), respond with {\"ok\": false, \"reason\": \"<why>\", \"impossible\": true}. Otherwise respond with {\"ok\": true}.",
"timeout": 30
}
]
}
]
}
}impossible を使うかどうかは、判定条件が「いつか満たせる」ものか「環境次第で満たせない」ものかで決まります。いつか満たせる条件だけなら、ok と reason の2つで足ります。
イベント別: ok: falseで何が起きるか
ok: false の扱いはイベントで分かれます。PreToolUseは、v2.1.210より前と今で既定の挙動が逆です。
| イベント | ok: false のときの既定 | continueOnBlock: true |
|---|---|---|
| Stop / SubagentStop | ok: false のときの既定reason を次の指示として戻し、継続 | continueOnBlock: true対象外(impossible: true で停止を許す) |
| PreToolUse | ok: false のときの既定ツール呼び出しを拒否し、ターンを終える。reason は警告として表示 | continueOnBlock: truereason をツールエラーとして返し、Claudeが調整して続ける |
| PostToolUse | ok: false のときの既定ターンを終える。reason は警告として表示 | continueOnBlock: truereason をClaudeに戻し、ターンを続ける |
| PostToolBatch / UserPromptSubmit / UserPromptExpansion | ok: false のときの既定ターンを終え、reason を警告として表示 | continueOnBlock: true指定しても同じ |
| PostToolUseFailure / TaskCreated | ok: false のときの既定reason をツールエラーとして返し、継続 | continueOnBlock: true指定の有無に関わらず継続 |
| TeammateIdle | ok: false のときの既定チームメイトが止まり、reason が警告として表示 | continueOnBlock: truereason を戻して作業を続けさせる |
| PermissionRequest | ok: false のときの既定効果なし | continueOnBlock: true効果なし |
| PermissionDenied | ok: false のときの既定効果なし(すでに拒否済み) | continueOnBlock: true効果なし |
TaskCompletedは、タスク完了のマークで発火したときは継続側、チームメイトの停止で発火したときはTeammateIdleと同じ動きです。
continueOnBlockの使い分け
continueOnBlock は、prompt hookの設定項目です。既定は falseで、「拒否したらターンごと終わる」動きになります。
- 危険な操作を見つけたら、人間に判断を戻したい: 既定の
falseのまま使う。PreToolUseで拒否され、理由が警告として表示される - 拒否の理由を踏まえてClaudeにやり直させたい:
trueにする。reasonがツールエラーとしてClaudeに返る
PreToolUseで continueOnBlock: true を使う例は次のとおりです。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "Block any Bash command that reads .env files. Input: $ARGUMENTS\nRespond with {\"ok\": false, \"reason\": \"<what to do instead>\"} to block, otherwise {\"ok\": true}.",
"continueOnBlock": true
}
]
}
]
}
}この設定なら、Claudeは reason を読んで別のコマンドへ切り替えられます。PreToolUseのこの既定は、v2.1.210より前は逆でした。当時は拒否の reason がツールエラーとして返り、ターンが続いていました。以前の挙動に合わせたい設定は、continueOnBlock: true を明示します。
modelとtimeout
prompt hookの設定項目は、次の5つです。
| 項目 | 必須 | 内容 |
|---|---|---|
type | 必須必須 | 内容"prompt" 固定 |
prompt | 必須必須 | 内容モデルに送る文。$ARGUMENTS で入力JSONを差し込む |
model | 必須任意 | 内容判断に使うモデル。省略するとバックグラウンド処理用のモデル |
timeout | 必須任意 | 内容秒数。既定は30 |
continueOnBlock | 必須任意 | 内容既定は false |
判断が込み入る場合は、model に能力の高いモデルを指定できます。判定のたびにモデル呼び出しが入るため、頻繁に発火するイベント(PreToolUseなど)では、モデルとプロンプトの長さが応答時間に直結します。
どのイベントで使えるか
prompt hookが使えるイベントは限られています。command・http・mcp_tool・prompt・agentの5種類すべてに対応するのは、PermissionDenied、PostToolBatch、PostToolUse、PostToolUseFailure、PreToolUse、Stop、SubagentStop、TaskCompleted、TaskCreated、TeammateIdle、UserPromptExpansion、UserPromptSubmitです。
PermissionRequestは、agent以外の4種類に対応します。SessionStartとSetupはcommandとmcp_toolだけです。ConfigChange、CwdChanged、Elicitation、MessageDisplay、Notification、StopFailure、SubagentStartなどは、command・http・mcp_toolのみで、prompt hookは書いても使えません。StopFailureの扱いはStopFailureフックの記事にあります。
prompt hookとagent hookの違い
agent hookは「ファイルを読む・コマンドを実行する」ところまで踏み込める点が違います。
| 比較軸 | prompt hook | agent hook |
|---|---|---|
| 実行 | prompt hookモデルを1回呼ぶ | agent hookサブエージェントが最大50ターン動く |
| 既定のタイムアウト | prompt hook30秒 | agent hook60秒 |
impossible | prompt hook使える(Stop・SubagentStop) | agent hook使えない |
continueOnBlock | prompt hook使える | agent hookフィールドなし |
ok: false の動き | prompt hookcontinueOnBlock 次第 | agent hookcontinueOnBlock: true のprompt hookと同じ |
フックの入力JSONだけで判断できるならprompt hook、コードベースの実際の状態を確かめる必要があるならagent hookです。agent hookは試験的な機能と位置づけられており、本番の運用ではcommandフックが推奨されています。テスト実行をStopで確かめる設計は、テスト自動実行のフック設計でPostToolUseとの使い分けを扱っています。
プロンプトの書き方 — 命令文でも条件文でも書ける
公式ドキュメントは、プロンプトを「何をブロックするか」という規則(Block any Bash command that reads .env files)としても、「満たすべき条件」(All unit tests pass)としても書けると説明しています。
命令文の書き方には、過去に不具合がありました。v2.1.294の更新で、命令文で書かれたprompt・agentフックが本来ブロックすべきものを通してしまう問題が修正されています。同じ版で、StopとSubagentStopの命令文(「ビルドが壊れていても続行する」のような文)の判定も改善され、Claudeが早く止まりにくくなりました。
古いバージョンで命令文のフックが効かないときは、まず更新を確認します。そのうえで、返すJSONの形をプロンプトに明示しておくと、判断の取りこぼしを避けやすくなります。
Stopで差し戻し続けるときの上限
Stopのフックには、連続で止めさせる回数の上限があります。Claude Codeは、Claudeのツール呼び出しを挟まずにStopフックが8回続けて差し戻すと、フックの判断を上書きしてターンを終えます。上限は「Stopフック」に対する記述で、フックの種類は限定されていません。
上限を超えて続ける設計が本当に必要なら、環境変数で引き上げられます。詳しくはCLAUDE_CODE_STOP_HOOK_BLOCK_CAPの記事で扱っています。上限を上げる前に、impossible で満たせない条件を逃がせないかを考えるほうが、無限ループの予防には合っています。同じプロンプトを繰り返し与え続ける公式プラグインの例は、ralph loopで確認できます。
よくあるつまずき
- PostToolUseで差し戻したのに、Claudeが続かない: 既定ではターンが終わる。
continueOnBlock: trueを付ける - PreToolUseの拒否で、Claudeが別の手を試さない: 同じく
continueOnBlock: trueを確認する。v2.1.210より前の記憶で書いた設定は、挙動が逆になっている impossibleを書いたのにPostToolUseで効かない: 効くのはStopとSubagentStopだけ- agent hookで
impossibleやcontinueOnBlockを書いた: agent hookはどちらのフィールドも持たない - SubagentStartなどにprompt hookを置いた: そのイベントはprompt型に非対応で、発火しない
reasonが空:ok: falseのときはreasonが必須。Stopではreasonがそのまま次の指示になるため、具体的な行動を書かせる- モデルが文章で答えてしまう: プロンプトの末尾に、返すJSONの形を例として明記する。
okが欠けた応答は判断として使えない
まとめ
prompt hookで覚えるのは、StopとSubagentStopでは「ok: false は継続、impossible: true を足すと停止」、PreToolUseとPostToolUseでは「既定は終了、continueOnBlock: true で継続」という2本立てです。判定にファイルやコマンドの確認が要るならagent hook、決定的に書けるならcommandフックへ切り替えます。フック全体の設計はHooks完全ガイドから辿れます。