
Claude Codeのフック(hooks)の使い方|settings.jsonの書き方と実用例5つ・Windowsでつまずく所まで、実機で確認【2026年10月】
「CLAUDE.md に『.env は触らないで』と書いたのに、書き換えられた」。「毎回『編集したらフォーマットして』と頼むのが面倒」。Claude Code を使い込むと、こんな場面に出会います。
CLAUDE.md の指示は、あくまで「お願い」です。守られることが多くても、AI が判断で破る可能性はゼロではありません。そこで使うのが、フック(hooks)です。
この記事では、フックとは何か、どう設定するか、実用例5つを、実際に動かして確かめた結果とあわせて説明します。
私は Claude Code と Codex を毎日使って、教材づくり、note・Gumroad への出品、画像づくりを自動化しています。私の作業場でも、フックを使っています(セッションの開始と、依頼を送るたびに、作業の一覧を AI に添える処理です。あとで書きます)。
この記事のフックは、読み物のために書いたものではなく、実際に動かして確かめたものです。Windows 11 の Claude Code で、ファイルの編集と危険なコマンドを止めるフックを入れ、AI に実際に頼んで、止められるところまで確認しました。そのときに Windows でつまずいた所も、そのまま書きます。全文無料です。
この記事で分かること
フックとは何か。CLAUDE.md の指示と何が違うのか
主なイベントと、いつ動くか
settings.json の書き方と、置き場所
終了コード 0 と 2、JSON で返す方法
実用例5つ(通知・自動整形・保護ファイルのブロック・危険コマンドのブロック・圧縮後の再注入)
実機で確かめた結果と、Windows でつまずいた文字コードの問題
動かないときの調べ方、安全の注意
先に結論
フックは、Claude Code の動きの決まったタイミングで、あなたが書いた処理を必ず実行する仕組みです。公式のドキュメントの言葉では、「AI が実行するかを選ぶのではなく、必ず起きる」ものです
設定は settings.json に書きます。「どのイベントで・どの道具に対して・何を実行するか」の3つを決めます
処理が終了コード 2 で終わると、その操作を止められます。標準エラー出力に書いた文は、AI へのフィードバックになります
「お願い」と「仕組み」の違いは大きい。絶対に守らせたいことは、CLAUDE.md ではなく、フックに書くのが基本です
私の環境では、権限の確認を省く設定にしていましたが、それでもフックの「止める」は効きました
Windows では、Python で書くと楽です。ただし、日本語を出力するときは文字コードに注意が要ります
フックとは
フックは、「Claude Code の動きの途中で、決まったタイミングに、自動で動く処理」です。公式のドキュメントでは、こう説明されています。
Hooks are user-defined shell commands. Claude Code runs them at specific points in its lifecycle, which gives you deterministic control: certain actions always happen rather than relying on the LLM to choose to run them.
つまり、「LLM(AI)に実行を選ばせるのではなく、必ず実行させる」ための仕組みです。
「お願い」と「仕組み」
CLAUDE.md に書くのは、AI へのお願いです。AI はそれを読んで、判断して動きます。多くは守られますが、長い作業の途中で抜けたり、状況によっては別の判断をしたりします。
フックは、AI の判断の外で動きます。AI が「.env を書き換えよう」と決めても、その直前にフックが走って、止められます。AI の気分に関係なく、必ず起きます。
使い分けは、こうです。
多少ぶれてもよい、考え方や文体:CLAUDE.md
絶対に守らせたい、守れなかったら困ること:フック
主なイベント
フックは、「いつ動くか」を、イベントで決めます。公式のドキュメントには多くのイベントが載っていますが、最初に覚えるのは、次の6つです。
SessionStart:セッションが始まったとき(再開したときも)
UserPromptSubmit:あなたが依頼を送ったとき。AI が読む前に動く
PreToolUse:AI が道具(ファイルの編集・コマンドの実行など)を使う直前。ここで止められる
PostToolUse:道具を使ったあと(成功したとき)
Notification:Claude Code が通知を出すとき(確認待ちなど)
Stop:AI が返答を終えたとき
ほかにも、PreCompact(会話の圧縮の前)、SubagentStop(サブエージェントの終了)、SessionEnd(終了)など、30を超えるイベントが用意されています。最初は、上の6つで足ります。
settings.json の書き方
フックは、設定ファイル(settings.json)に書きます。基本の形は、次のとおりです。
{
"hooks": {
"イベント名": [
{
"matcher": "対象の道具の名前",
"hooks": [
{
"type": "command",
"command": "実行するコマンド"
}
]
}
]
}
}matcher:どの道具のときに動くか
matcher は、「どの場合に動くか」の絞り込みです。PreToolUse や PostToolUse では、道具の名前を書きます。
"Bash":コマンドを実行するとき
"Edit|Write":ファイルを編集・作成するとき(| は「または」)
空("")か省略:そのイベントのすべての場合
絞らないと、すべての道具の使用のたびに動きます。できるだけ狭く絞ります。
置き場所
公式のドキュメントでは、場所によって、効く範囲が変わります。
~/.claude/settings.json:自分の全プロジェクト。自分のパソコンだけ
.claude/settings.json:そのプロジェクト。Git に入れれば、チームで共有できる
.claude/settings.local.json:そのプロジェクトだけ。共有しない
設定したあと、Claude Code の中で /hooks と打つと、設定されているフックの一覧が出ます。書いたあとは、まず /hooks で、登録されているかを確かめるのが、確実です。
フックの入力と出力
フックの処理(コマンド)は、標準入力から JSON を受け取り、終了コードで答えます。
入力
イベントが起きると、Claude Code が、状況の JSON をコマンドの標準入力に渡します。たとえば、PreToolUse(Bash)で npm test を実行しようとしているとき、こんな JSON が渡されます。
{
"session_id": "abc123",
"cwd": "/Users/sarah/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}ファイルの編集なら、tool_input の中に file_path が入ります。UserPromptSubmit なら、送った文(prompt)が入ります。
出力:終了コード
終了コードで、次の動きが決まります。
終了コード 0:異論なし。PreToolUse の場合でも、これは「許可」ではなく、通常の許可の流れに進むだけです
終了コード 2:その操作を止める。標準エラー出力に書いた理由が、AI へのフィードバックとして渡る
それ以外:多くのイベントでは、エラーとして扱われるが、動きは止まらない
出力:JSON で返す
終了コード 0 のまま、JSON を標準出力に出すと、もっと細かく制御できます。たとえば PreToolUse で、操作を断る場合です。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg instead of grep for better performance"
}
}permissionDecision は、allow(確認を省く)・deny(取り消して、理由を AI に返す)・ask(普通に確認する)の3つです。
2つの注意が、公式のドキュメントにあります。
終了コード 2 で止める方法と、JSON で返す方法は、1つのフックの中でどちらか一方にする(混ぜない)
permissionDecision は、hookSpecificOutput の中に入れる。外側に置いても、JSON としては読まれますが、効きません(エラーも出ません)

実用例5つ
例1:確認待ちを通知する(Notification)
AI が許可を待って止まっているとき、デスクトップに通知を出す。長い作業を任せて、別のことをしているときに便利です。公式のドキュメントの例を、使いやすい形で書きます。
macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}Windows(PowerShell):
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
}
]
}
]
}
}公式のドキュメントによると、Windows のこの例は、画面の隅の通知ではなく、ダイアログを開きます。ターミナルの後ろに隠れることがあるそうです。動かないときは、まずその命令を PowerShell で直接実行して確かめてください。
例2:編集したら、自動で整形する(PostToolUse)
AI がファイルを編集したあと、自動でフォーマッタ(Prettier など)を走らせます。「編集したら整形して」と毎回頼まなくてよくなります。公式のドキュメントの例です。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}jq は、JSON から値を取り出すコマンドです。Windows には最初から入っていないので、入れるか、次の例のように Python で書き換えてください。
例3:保護ファイルの編集を止める(PreToolUse・終了コード 2)
.env(鍵が入っている)、package-lock.json、.git/ の中身を、AI に触らせない例です。次の Python のスクリプトを、.claude/hooks/protect_files.py に置きます。
#!/usr/bin/env python3
"""PreToolUse: 触ってほしくないファイルへの Edit / Write を止める"""
import json
import sys
sys.stderr.reconfigure(encoding="utf-8") # Windows の既定の文字コードで日本語が化けないように
PROTECTED = [".env", "package-lock.json", ".git/"]
data = json.load(sys.stdin.buffer) # 入力は UTF-8 の JSON
path = (data.get("tool_input", {}).get("file_path") or "").replace("\\", "/")
for pattern in PROTECTED:
if pattern in path:
# 標準エラー出力に書いた文が、そのまま Claude へのフィードバックになる
print(f"Blocked: {path} は保護されているファイルです({pattern})。別の方法を考えてください", file=sys.stderr)
sys.exit(2) # 終了コード 2 = この操作を止める
sys.exit(0) # 0 = 異論なし(通常の許可の流れに進む).replace("\\", "/") は、Windows のパスの区切り(\)を、/ にそろえるためです。公式の例(シェルスクリプト)にも、同じ理由の一行があります。
例4:危険なコマンドを止める(PreToolUse・JSON)
rm -rf、git push --force、git reset --hard のような、取り返しのつかないコマンドを止める例です。こちらは、JSON で返す方法で書きます。.claude/hooks/block_dangerous.py です。
#!/usr/bin/env python3
"""PreToolUse(Bash): 取り返しのつかないコマンドを止める"""
import json
import re
import sys
RULES = [
(r"\brm\s+-\w*r\w*f|\brm\s+-\w*f\w*r", "rm -rf は使わないでください。消すものを1つずつ指定してください"),
(r"\bgit\s+push\b.*(--force\b|\s-f\b)", "force push は止めています"),
(r"\bgit\s+reset\s+--hard\b", "git reset --hard は止めています。先に git stash で退避してください"),
]
data = json.load(sys.stdin.buffer) # 入力は UTF-8 の JSON
command = (data.get("tool_input") or {}).get("command", "")
for pattern, reason in RULES:
if re.search(pattern, command, re.IGNORECASE):
# 終了コード 0 のまま JSON を出す方法。理由は Claude に返る
# ensure_ascii の既定(True)なら日本語は \uXXXX になり、Windows でも化けない
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason,
}
}))
sys.exit(0)
sys.exit(0)ルールは完璧ではありません。たとえば rm -r -f(オプションを分けて書く)は、この正規表現ではすり抜けます。最初は少ないルールで始めて、実際に困ったものを足していくのが現実的です。
登録する
2つのスクリプトを、.claude/settings.json に登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect_files.py\""
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/block_dangerous.py\""
}
]
}
]
}
}$CLAUDE_PROJECT_DIR は、プロジェクトのフォルダを指す変数です。Windows 11 でも、この書き方で動きました。
実機で確かめた結果
この設定のフォルダで、Claude Code に、わざと止められることを頼みました。
1つ目は、「rm -rf build を実行して」と頼みました。AI の答えは、こうでした。
拒否されました。理由:「rm -rf は使わないでください。消すものを1つずつ指定してください」フックが書いた理由が、日本語のまま AI に届いています。
2つ目は、「.env というファイルに KEY=1 と書き込んで」と頼みました。AI は、「.env ファイルは保護されているため書き込みができません」と答えました。実際に、.env は作られませんでした。
なお、私の環境は、権限の確認を省く設定(bypassPermissions)にしてありました。それでも、フックの「止める」は効きました。公式のドキュメントにも、「PreToolUse のフックは、どの権限モードよりも先に動き、deny は権限確認を省く設定でも効く」と書かれています。「確認を省く設定」にしているときこそ、フックが安全網になります。
例5:会話の圧縮後に、大事な決まりごとを入れ直す(SessionStart)
会話が長くなると、Claude Code は、過去のやりとりを要約して圧縮します。そのとき、細かい決まりごとが抜けることがあります。SessionStart の compact という matcher を使うと、圧縮のあとに、決まりごとを入れ直せます。公式のドキュメントの例です。
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}標準出力に書いた文が、そのまま AI の記憶に足されます(SessionStart と UserPromptSubmit の場合)。
私の使い方:作業の一覧を、毎回 AI に添える
私の作業場でも、フックを2つ使っています。SessionStart と UserPromptSubmit の両方で、同じ処理(自作のコマンド)を走らせています。
やっていることは、こうです。私は、「依頼」「タスク」「質問」などを、画面(ボード)に書いて管理しています。セッションが始まったとき、そして私が何か送るたびに、フックが走ります。そして、ボードに新しく足した項目や、変えた項目があれば、それを AI のメッセージに添えます。
これで、会話の途中で私がボードに項目を足しても、次のメッセージで AI に伝わります。AI に「ボードを見て」と言う必要がありません。
CLAUDE.md に「ボードを見て」と書く方法もあります。でも、それは「お願い」です。フックにすると、AI が見るのを忘れても、必ず届きます。ここが、フックのいちばんの強みです。
Windows でつまずいた所:文字コード
この記事のスクリプトを作るとき、最初の版では、Windows で次の問題にぶつかりました。
問題:日本語の出力が、化けた
最初は、理由の日本語を、そのまま print() で出力していました(JSON のほうは ensure_ascii=False でした)。模擬の入力を流して出力を見ると、日本語が文字化けしていました。Python の標準出力は、Windows では、既定でその PC の文字コード(日本語環境では Shift_JIS 系)になるためです。
Claude Code は、フックの出力を UTF-8 として読むはずなので、この状態では食い違うおそれがあります。私は、この版を実際の Claude Code にかける前に直したので、「化けたまま届く」ところまでは確かめていません。直したあとの版は、次のとおり、日本語の理由が正しく AI に届きました。
直し方
標準エラー出力(終了コード 2 の方法):sys.stderr.reconfigure(encoding="utf-8") を、先頭に1行入れる
JSON で返す方法:json.dumps() の既定(ensure_ascii=True)のままにする。日本語は \uXXXX に変換されて、どの文字コードでも壊れません。ensure_ascii=False にしないでください
標準入力の読み込み:json.load(sys.stdin) ではなく、json.load(sys.stdin.buffer) にする。念のための対策です。バイト列のまま読めば、日本語のファイル名やパスが入っていても、UTF-8 として読めます
上のスクリプトは、直したあとのものです。
jq が入っていない
公式の例は、jq を使っています。Windows には、最初から入っていません。Python で書けば、追加のものを入れずに、同じことができます。
動かすのに、シェルの違いはある?
私の環境では、python "$CLAUDE_PROJECT_DIR/.claude/hooks/xxx.py" という書き方で動きました。ただし、環境によって、フックのコマンドを実行するシェルが違うことがあります。動かないときは、まず、コマンド単体をターミナルで実行してください。
動かないときの調べ方
1. /hooks で登録されているか見る
一覧に出ていなければ、settings.json の場所や書式が間違っています。JSON のカンマや括弧のミスも多いです。
2. matcher を見直す
Edit|Write のように、道具の名前が正しいか。大文字・小文字も区別されます。
3. コマンドを単体で動かす
標準入力に JSON を流して、終了コードを確かめます。
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' | python .claude/hooks/block_dangerous.py
echo $?4. デバッグログを見る
claude --debug で起動すると、フックの終了コードや出力のログが見られます。公式のドキュメントでも、これが勧められています。
5. JSON の項目の場所を見直す
permissionDecision や additionalContext を、hookSpecificOutput の外側に置いても、エラーは出ません。ただ、効きません。「JSON を返しているのに、何も起きない」ときは、まずここを疑います。
安全のために
フックは、あなたの権限で、あなたのパソコンのコマンドを実行します。便利な分、扱いには注意が要ります。
知らないリポジトリの .claude/settings.json に、フックが書かれていないか確かめる:他人のプロジェクトを開くとき、そこの設定に書かれたフックは、コマンドとして実行されます。中身を見てから使ってください
フックの中で、鍵や個人情報をログに残さない:標準入力の JSON には、ファイルの中身や入力した文が入ることがあります
止めるフックは、最初は少なく:厳しすぎると、AI が必要な作業までできなくなります。実際に困ったものを、1つずつ足します
フックが遅いと、その分、全体が遅くなる:重い処理は、timeout を決めるか、別の方法にします
よくある疑問
Q. フックと、権限の設定(許可・拒否のルール)は、何が違いますか?
権限の設定は、「この道具を使ってよいか」を、名前や形式で決めるものです。フックは、もっと細かく、「中身を見て」判断したり、自動で別の処理を走らせたりできます。両方を組み合わせて使います。
Q. フックを書くのが難しそうです
Claude Code 自身に、「.env を編集しようとしたら止めるフックを作って」と頼めば、書いてくれます(公式のドキュメントにも、AI に書かせる方法が載っています)。そのうえで、この記事のように、実際に止まるかを試してください。
Q. 複数のフックが、同じイベントで動くと?
同じイベントに合うフックは、並列で動きます。PreToolUse で結果が食い違った場合は、いちばん厳しいもの(deny → ask → allow の順)が採用されます。1つが deny を返しても、他のフックの処理も最後まで動きます。止めたいときに、他のフックの副作用に頼らないでください。
注意点
この記事の内容は、2026年10月15日の公式ドキュメントと、私の Windows 11 の環境で確かめたものです。イベントや項目は、版が上がると変わることがあります。迷ったら /hooks と、公式のドキュメントを見てください
危険コマンドのルールは、完璧ではありません。あくまで「うっかり」を防ぐ網です。悪意ある操作を防ぐ仕組みとして、これだけに頼らないでください
フックの設定ミスで、必要な作業まで止まることがあります。止まったら、/hooks で確認し、必要ならいったん外してください
まとめ
フックは、Claude Code の動きの決まったタイミングで、必ず動く処理。CLAUDE.md の「お願い」と違い、AI の判断の外で動く
最初に覚えるイベントは、SessionStart・UserPromptSubmit・PreToolUse・PostToolUse・Notification・Stop の6つ
設定は settings.json。イベント → matcher → コマンドの順。書いたら /hooks で登録を確かめる
処理は、標準入力で JSON を受け取り、終了コード 2(止める)か、JSON(permissionDecision)で答える。この2つは混ぜない
実用例は、通知・自動整形・保護ファイルのブロック(例3)・危険コマンドのブロック(例4)・圧縮後の再注入。例3と例4を実機で確かめた
権限の確認を省く設定でも、PreToolUse の deny は効く
Windows では、Python で書く。標準入力は sys.stdin.buffer、標準エラーは reconfigure(encoding="utf-8")、JSON は ensure_ascii の既定のまま
知らないリポジトリのフックは、実行される前に中身を見る
次の一歩は、.env を編集しようとしたら止まるフックを1つ入れて、AI に頼んで、本当に止まるかを試すことです。10分で、できます。
次に読む
Claude Codeの使い方・始め方入門(無料):インストールから最初の依頼まで
CLAUDE.mdの書き方(無料):「お願い」を、短く具体的に書く方法
AIエージェントとは(無料):コードを書いて、動かして、直すAIの考え方
Claude Code テンプレート集(¥9,800):CLAUDE.md・スキル12本・フック10本(動作確認済みスクリプト付き)・プロンプト100本の実物パック
このアカウントをフォローすると、Claude Code や Codex の新しい手順の記事が届きます。ぜひフォローしてください。
この先で分かること(今後の有料記事)
そのままコピーして使えるフックの実物(保護・通知・整形・記録・上限のチェックなど)
自動実行(claude -p)の中で、フックを安全網にする設計
実際に起きた失敗と、直し方の記録
まさのAI実務ラボ|Codex・Claude Code では、有料記事が読み放題になります。テンプレートや指示文をまとめて使いたい方はご覧ください。