Claude Codeの仕組み講座(講師のためのエンジニア水準)
区分3 拡張の仕組み★★★Claude Code は、仕事の途中にいくつもの「節目」を通ります。道具を使う前、道具を使った後、返事を終えた時、などです。この節目を、イベントと呼びます。
フックは、イベントが起きた時に、あなたが指定した命令を自動で動かす仕掛けです。動かすのはAIではなく、Claude Code 本体です。
事務所でいえば、入口の「入退室のたびに鳴るチャイム」です。誰がどんな気分で通っても、通れば必ず鳴ります。スキル(No.09)は新人が判断して読むマニュアルですが、フックは新人の判断を待ちません。
0 は成功、それ以外は失敗や合図この単元では、これらが何度も出てきます。
公式には、30を超えるイベントがあります。「いつ動くか」と「止められるか」の2点で、よく使う物を押さえます。
| イベント | 動く瞬間 | 止められるか |
|---|---|---|
SessionStart |
会話が始まった・再開した時 | 止められない |
UserPromptSubmit |
あなたが依頼を送った直後、AIが読む前 | 止められる |
PreToolUse |
道具を使う直前 | 止められる |
PermissionRequest |
許可の確認が出る時 | 確認に答えられる |
PostToolUse |
道具を使い終えた直後(成功時) | 止められない(結果を知らせることはできる) |
Notification |
Claude Code が通知を出す時 | 止められない |
Stop |
Claude が返事を終えようとした時 | 止められる(続けさせる) |
SubagentStop |
サブエージェント(No.12)が終わる時 | 止められる |
PreCompact |
会話を要約する直前 | 止められる |
SessionEnd |
会話が終わる時 | 止められない |
Pre は「前」、Post は「後」、ToolUse は「道具を使うこと」です。名前を読めば、いつ動くかが分かります。
PreToolUse で止める=道具を使う前に止めて、AIに理由を返す、です。Stop で止める=返事を終わらせず、続けさせる、です。
PostToolUse は、道具がもう実行された後なので、巻き戻しはできません。結果を見て「こう直して」とAIに伝える使い方になります。
フックは、設定ファイルの hooks という欄に書きます。
| 場所 | 効く範囲 |
|---|---|
~/.claude/settings.json |
自分の全作業場 |
.claude/settings.json |
その作業場(共有できる) |
.claude/settings.local.json |
その作業場・自分だけ |
プラグインの hooks/hooks.json |
プラグインを有効にした所(No.12) |
「作業場」は、いま開いているフォルダ(公式では「プロジェクト」)のことです。複数の場所に書いたフックは、上書きされず、全部が合わさって動きます。
書き方は、「イベント → 条件の組 → 実行する物」の3段です。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/check.sh" }
]
}
]
}
}
matcher(絞り込みの条件)は、「どの道具の時に動かすか」を決めます。決まりは3つです。
*:全部の場合_・-・区切りの | だけ:完全一致。Edit|Write なら「Edit か Write」^Notebook と書くMCP(No.11)の道具は mcp__サーバー名__道具名 という名前になります。サーバー全部に当てるなら、mcp__memory__.* のように末尾に .* を付けます。matcher に対応しないイベントでは、matcher は黙って無視されます。
type に書く値で、動かす物を選べます。
command:命令(シェル=命令を受け付ける窓口に渡すコマンド)を動かす。いちばん多いhttp:指定したURLに情報を送るmcp_tool:MCPの道具を呼ぶprompt:AI(モデル)に1回だけ判断させる。判断が要る時agent:道具を使って確かめる小さなエージェント。試験的な機能動かす時間の上限は、命令・http・mcp_tool で600秒が初期値です。ただし UserPromptSubmit では30秒です。依頼を送るたびに動くので、短く設定されています。
フックは、情報を受け取って、返事を返す小さなプログラムです。
JSON(ジェイソン)は、項目と値を { } で書く、プログラム同士が使う書式です。フックには、いまの状況がJSONで標準入力に流し込まれます。
共通で入る項目は次の通りです。
session_id:会話の識別番号cwd:いま開いているフォルダpermission_mode:許可モード(No.13)hook_event_name:どのイベントかtranscript_path:会話の記録ファイルの場所PreToolUse なら、さらにこう入ります。
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" },
"tool_use_id": "toolu_01..."
}
つまりフックは、「どの道具を、どんな引数(道具に渡す値。ここでは実行する命令)で使おうとしているか」を、実行の直前に見られます。これが、止めたり、書き換えたりできる理由です。
返事は、終了コードか、JSONのどちらか1つで返します。混ぜません。
終了コードで返す
| 終了コード | 意味 |
|---|---|
0 |
成功。何も言わなければ「判断なし」で、通常の流れが続く |
2 |
止めてほしい。標準エラー出力(エラー用の出力口。stderr とも書く)の文章が、理由としてAIに渡る |
| それ以外 | 失敗だが、止めない。エラーの知らせが出るだけで処理は続く |
大事なのは、1 では止まらないという点です。うっかり exit 1 で終わるフックは、止める役を果たしません。
原則は「フックの失敗は通す側に倒れる(fail-open)」です。時間切れになった時も、終了コード1などで終わった時も、道具はそのまま実行されます(公式:「On most events, when a hook fails or times out, Claude Code still carries out the action」)。確実に止めたいフックは、止める時に必ず終了コード2か拒否のJSONで終わらせます。
JSONで返す
標準出力(普通の出力口)に、JSONを1つだけ出します。たとえば PreToolUse で拒否するなら、こう返します。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "データベースへの書き込みは禁止"
}
}
permissionDecision には、allow(許可)・deny(拒否)・ask(本人に確認)などを入れます。updatedInput で、道具に渡す引数を書き換えることもできます。additionalContext に文章を入れると、AIへの補足として机に置かれます(上限は1万文字)。
ここで注意したいのは、フックの allow は許可の確認を省くだけで、設定の拒否ルールを越えられないことです。公式には「Deny and ask rules are still evaluated」とあり、拒否ルールに当たる道具は、フックが allow を返しても止まります。ルールの判定順は No.13 で扱います。
公式の例は、rm -rf(フォルダを中身ごと消す命令)を含む命令を止めるフックです。シェルスクリプト(命令を並べたファイル)で書きます。
#!/bin/bash # 「このファイルはbashで動かす」という宣言
# jq は、JSONから項目を取り出す道具。echo は、文字をそのまま出す命令。grep は、文字を探す道具
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",
permissionDecision:"deny",
permissionDecisionReason:"破壊的な命令は止めました"}}'
else
exit 0
fi
流れは3歩です。標準入力のJSONから命令を取り出す → 危ない文字が入っていたら拒否のJSONを出す → そうでなければ何もせず 0 で終わる、です。
時間切れ(タイムアウト)になった PreToolUse のフックも、道具をそのまま実行させます。止める役のフックが固まると、止まらずに通ってしまうので、講義で伝える価値があります。なお公式には、設定に "onFailure": "block" を足すと失敗時も止められる、とあります(v2.1.295以降)。
あなたの環境では、~/.claude/settings.json に4組のフックが入っています。
PreToolUse と PostToolUse:ブラウザ操作の道具(mcp__claude-in-chrome__ で始まる物)の前後に動くUserPromptSubmit:依頼を送った時に動くStop:返事を終えようとした時に動くどれも、イベントの瞬間にだけ動く小さなスクリプトです。中身を開いて、読み取っている項目を探してみると、仕組みがよく分かります。
フックの強さの順番は、フックの deny は他より優先、allow は拒否ルールを越えられない、という形です。機能ごとの比較は No.15 で扱います。
○ 受講生によく聞かれる質問:「CLAUDE.md に『このファイルは触るな』と書いたのに、触られました」
→ 答え方:CLAUDE.md は読み物で、強制ではありません。確実に止めたいなら PreToolUse のフックで、終了コード2か拒否のJSONを返します。フックはAIの判断より前に、本体が動かします
○ 受講生によく聞かれる質問:「フックを入れたら、会話が遅くなりました」
→ 答え方:UserPromptSubmit などは、依頼を送るたびに待ちが発生します。重い処理を入れていないか、動く時間の上限が長すぎないかを見ます
○ 講義で使える図のたとえ:入口のチャイムと警備員です。チャイム(PostToolUse)は鳴らすだけ。警備員(PreToolUse)は、通す前に身分証を見て、止められます。
○ 関係する単元:No.09 スキル(判断して動く側)、No.13 許可の判定順
フック=イベントの瞬間に本体が動かす。状況はJSONが標準入力で渡る。止めるのは終了コード2か、拒否のJSON。終了コード1では止まらない。
問1 「危険な命令は、実行される前に必ず止めたい」。使うイベントは?
問2 止めるつもりのフックが、命令を止めてくれませんでした。原因の候補で正しいのは?
問3 「Edit と Write の2つの道具の時だけ動かす」matcher の書き方として正しいのは?
問4 フックに渡される情報と、その使い方で正しいのは?
出典:Claude Code 公式ドキュメント「Hooks reference」(https://code.claude.com/docs/en/hooks)/「Automate actions with hooks」(https://code.claude.com/docs/en/hooks-guide)/「Extend Claude Code」(https://code.claude.com/docs/en/features-overview)/手元の ~/.claude/settings.json の hooks の実物(命令の中身は載せない)。数字は上記の公式による