こんにちは、ハックです。AIを相棒に、非エンジニアから今では様々なツールやアプリを開発をしている一人社長です。
「Claude Codeの作業が終わっていたのに気がつかなかった」
「CLAUDE.mdにルールを書いたのに、守ってくれない時がある」
Claude Codeを使い込むほど、こういう場面が増えてきますよね。
結論から言うと、この2つを解決するのがhooks(フック)です。hooksは、Claude Codeの動きの決まったタイミングで、あなたが指定したコマンドを必ず実行する仕組みです。
CLAUDE.mdが「お願い」なのに対して、hooksは「仕組み」なので、Claudeの気分に左右されません。
この記事では、hooksの基本と設定方法、Macで通知と音を出す手順、僕が何かトラブルが起こるたびに追加してきたフックの実例を、コピーして使える設定テンプレートつきで紹介します。
僕は現在、約20個のフックを設定してる。どちらかというと問題が起きた際に歯止めとして設定したものが多いかな。僕も最初は通知設定から試してみたので、この記事もその順で紹介しようと思う。
はい、試すならまずは「Macで通知と音を出す方法」の章を参考にするのがおすすめですね!設定ファイルに数行を追加して、/hooksで確認するだけなので、簡単に設定できます。
🧭 読み終えたとき、あなたはこうなっています
- hooksとCLAUDE.md・Skillsの違いがわかり、どれに何を任せるかを決められる
- SessionStart・PreToolUse・PostToolUse・Stop・Notificationなど、主なイベントがいつ発火するかがわかる
settings.jsonのhooksブロック(matcher・command・exit code 2)を自分で書ける- Macで通知バナーと音(osascript・afplay)を出す設定をコピーして使える
- 「本番への書き込みを止める」「同じ記事を2本作らない」など、実運用している7つのフックの設定例がわかる
⚠️ この記事の信頼性について
Claude Code hooksとは


Claude Codeのhooks(フック)とは、Claude Codeの動きの特定のタイミングで、自動的に実行されるコマンドのことです。公式ドキュメントでは「ユーザー定義のシェルコマンド」と説明されています。
「タイミング」とは、たとえば次のような場面です。
- セッションを始めたとき
- あなたがプロンプトを送信した直後
- Claudeがファイルを編集したり、コマンドを実行したりする直前・直後
- Claudeが応答を終えたとき
この場面ごとに「このコマンドを実行する」と登録しておくと、Claude Codeが該当の場面に来るたびに、そのコマンドが動きます。公式ドキュメントは、hooksを「LLMが実行を選択するのに依存するのではなく、特定のアクションが常に発生する」仕組みだと説明しています。
シェルコマンドとは、ターミナルに打ち込む命令のことです。ls(ファイルの一覧を見る)やecho(文字を表示する)などがあります。hooksには、こうしたコマンドや、コマンドを並べて保存したスクリプト(.shファイル)を登録します。
hooksでできることは、大きく3つに分かれます。
| やりたいこと | 使うイベントの例 | 具体例 |
|---|---|---|
| 知らせる | Notification・Stop | 作業が終わったらMacの通知と音を出す |
| 止める | PreToolUse | 危険なコマンドやファイルの編集を実行前に止める |
| 整える・記録する | PostToolUse・SessionStart | 編集後に自動整形する・起動時に前回の続きを読み込む |
hooks と CLAUDE.md・Skills の違い
hooksとよく比べられるのが、CLAUDE.mdとSkills(スキル)です。違いは、「誰が実行するか」にあります。
| 実行するのは | 守られる確実さ | 向いているもの | |
|---|---|---|---|
| CLAUDE.md | Claude(読んで判断する) | Claudeの判断しだい | 方針・書き方・背景の説明 |
| Skills | Claude(必要なときに呼び出す) | Claudeが使うと判断したとき | 手順書・定型作業 |
| hooks | Claude Code本体(決まったタイミングで自動実行) | 毎回必ず | 通知・禁止・記録・自動整形 |
CLAUDE.mdに「本番には直接書き込まない」と書いても、Claudeはその文を読んで判断する側です。長い作業の途中で、ルールの優先度が下がってしまうことがあります。
一方でhooksは、Claudeの判断とは関係なく動きます。公式ドキュメントによると、PreToolUseフックがpermissionDecision: "deny"を返せば、bypassPermissionsモード(権限確認を飛ばすモード)や--dangerously-skip-permissionsで起動していても、そのツール呼び出しは止まります。「守らせたい」ことは、CLAUDE.mdではなくhooksの担当です。
ただし、なんでもhooksにすればいいわけではありません。「この書き方で統一したい」「このプロジェクトの背景はこうだ」といった伝えたいことは、CLAUDE.mdのほうが向いています。
公式ドキュメントも、すべてのセッション開始時にコンテキストを追加したいなら、CLAUDE.mdの使用を検討するよう案内しています。
CLAUDE.mdの書き方とSkillsの使い方は、以下の記事でまとめています。




Claude Code hooksの種類とイベント一覧


hooksの「タイミング」のことを、イベントと呼びます。公式リファレンスの一覧表には、2026年10月1日時点で33種類のイベントが載っています。
全部を覚える必要はありません。多くの人が使うのは、次の7つ前後です。
よく使う4つ:SessionStart・PreToolUse・PostToolUse・Stop
| イベント | 発火するタイミング | 止められる? |
|---|---|---|
SessionStart | セッションが始まったとき・再開したとき | 止められない |
PreToolUse | ツール呼び出し(ファイル編集・コマンド実行など)が実行される前 | 止められる |
PostToolUse | ツール呼び出しが成功した後 | 止められない(すでに実行済みのため) |
Stop | Claudeが応答を終えたとき | 止められる(Claudeが作業を続ける) |
それぞれ、使い方のイメージは次のとおりです。
SessionStart:起動した瞬間に動きます。matcher(後の章で説明します)で、startup(新しく起動)・resume(再開)・clear(/clearの後)・compact(会話の圧縮後)に絞れます。標準出力に書いた文字は、Claudeの文脈に追加されます。前回の続きを読み込ませる用途に向いています。
PreToolUse:最も使う機会が多いイベントです。Claudeがツールを使おうとした瞬間に、そのツール名と引数をフックが受け取ります。ここでexit 2(後で説明します)を返すと、ツールの実行そのものがキャンセルされます。
PostToolUse:ファイルを編集した後にPrettier(コードの整形ツール)を実行する、といった「後始末」に向いています。公式ドキュメントにあるとおり、ツールはすでに実行されているので、操作を元に戻すことはできません。
Stop:Claudeが応答を終えるたびに発火します。公式ドキュメントには、タスクが完了したときだけではなく、応答を終えるたびに発火すると書かれています。また、あなたが途中で割り込んだときには発火しません。
Notification・UserPromptSubmit・PreCompact
| イベント | 発火するタイミング | 使い道 |
|---|---|---|
Notification | Claude Codeが通知を送るとき(許可を待っている・入力を待っている等) | 通知・音 |
UserPromptSubmit | あなたがプロンプトを送信した直後、Claudeが処理する前 | 依頼ごとにルールを添える |
PreCompact | 会話の圧縮(/compactや自動圧縮)の前 | 圧縮前の処理 |
Notificationは、matcherで通知の種類を絞れます。代表的なものは次の2つです。
| matcher | 発火するタイミング |
|---|---|
permission_prompt | Claudeがツールの使用許可を求めていて、約6秒待っている |
idle_prompt | Claudeが約60秒前に応答を終えて、その後あなたが何も入力していない |
UserPromptSubmitは、matcherに対応していません。プロンプトを送信するたびに、必ず発火します。標準出力に書いた文字は、Claudeの文脈に追加されます。
そのほかのイベント
残りのイベントは、表で名前だけ紹介します。必要になったときに、公式リファレンスで確認してください。
| イベント | 発火するタイミング |
|---|---|
PermissionRequest | ツール呼び出しに許可の判断が必要なとき |
PostToolUseFailure | ツール呼び出しが失敗した後 |
SubagentStart・SubagentStop | サブエージェントの開始・終了 |
StopFailure | APIエラーでターンが終わったとき |
SessionEnd | セッションが終了したとき |
ConfigChange | セッション中に設定ファイルが変わったとき |
CwdChanged・FileChanged | 作業ディレクトリ・監視中のファイルが変わったとき |
PostCompact | 会話の圧縮が終わった後 |
このほか、エージェントチーム・Worktree・MCPの入力要求などのイベントもあります。この記事では扱いません。
hooksの「ハンドラー」(実行される中身)には、シェルコマンドのほかに、HTTPで外部サービスへ送る型、LLM(Claudeのモデル)に判断させるプロンプト型などもあります。この記事では、最も使われるシェルコマンド型("type": "command")だけを説明します。
Claude Code hooksの設定方法と書き方


hooksは、settings.json(Claude Codeの設定ファイル)にhooksブロックを追加して設定します。書き方は、次の3段の入れ子です。
- どのイベントに反応するか(
PreToolUse・Stopなど) - どんなときに発火させるかを絞るmatcher(「Bashツールのときだけ」など)
- 発火したときに実行するcommand(実行するコマンド)
一番小さい形は、次のようになります。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'Bashが実行されます' >&2"
}
]
}
]
}
}
"PreToolUse"がイベント、"matcher": "Bash"が絞り込み、"command"が実行する内容です。matcherの階層と、その中のhooks配列の階層が2つあるのが、最初につまずきやすい点です。「イベント → matcherのグループ → 実行するもの」の順に入れ子になっている、と覚えてください。
hooks を書く場所(ユーザー・プロジェクト)
hooksを書く場所で、効く範囲が変わります。
| 書く場所 | 効く範囲 | 共有 |
|---|---|---|
~/.claude/settings.json | 自分のすべてのプロジェクト | しない(自分のMacだけ) |
.claude/settings.json | そのプロジェクトだけ | できる(リポジトリに含められる) |
.claude/settings.local.json | そのプロジェクトだけ | しない(Claude Codeが作るときにgitで無視される) |
通知のように「どのプロジェクトでも欲しい」ものは~/.claude/settings.jsonに、「このプロジェクトのルール」は.claude/settings.jsonに書きます。このほか、組織の管理者が配る管理ポリシー設定や、プラグイン・スキルの中にhooksを持たせる方法もあります。
すでにhooksキーがある場合は、hooksオブジェクト全体を置き換えるのではなく、既存のイベントのキーの隣に新しいイベントを追加します。各イベント名は、ひとつのhooksオブジェクトの中のキーです。
settings.jsonそのものの場所や、設定の優先順位は、以下の記事で詳しく説明しています。


matcher と command の書き方
matcherは、フックを発火させる条件の絞り込みです。何に対して絞り込むかは、イベントによって違います。
| イベント | matcherが絞り込む対象 | 値の例 | |
|---|---|---|---|
PreToolUse・PostToolUse | ツール名 | Bash・`Edit\ | Write` |
SessionStart | セッションの始まり方 | startup・resume・compact | |
Notification | 通知の種類 | permission_prompt・idle_prompt | |
UserPromptSubmit・Stop | matcher非対応(毎回発火) | (書いても無視される) |
matcherの値の評価のされ方には、ルールがあります。
| matcherの書き方 | 評価のされ方 | ||
|---|---|---|---|
"*"・""・省略 | すべてに一致 | ||
英数字・_・-・スペース・,・`\ | `だけ | 完全一致(`\ | や,`で区切ると、どれか1つに完全一致) |
| それ以外の文字を含む | JavaScriptの正規表現(文字列のどこかに一致すれば発火) |
たとえばEdit|Writeは、EditツールかWriteツールのどちらかに完全一致します。一方、Edit.*のように記号を含めると正規表現として扱われ、EditとNotebookEditの両方に一致します。完全一致させたいときは^Edit$のように^と$で挟みます。また、matcherは大文字小文字を区別します。
matcherの絞り込みに加えて、ツールのイベントではifフィールドも使えます。"if": "Bash(git *)"のように、権限ルールと同じ書き方でコマンドの中身まで絞れるので、gitコマンドのときだけスクリプトを動かせます。ただし公式ドキュメントによると、ifはあくまでベストエフォートの絞り込みです。確実な許可・拒否が必要な場面では、権限システム(permissions)の使用が案内されています。
commandは、実行するコマンドです。ワンライナー(1行のコマンド)を直接書くことも、スクリプトのパスを書くこともできます。スクリプトを指すときは、プロジェクトのルートを表す$CLAUDE_PROJECT_DIRが使えます。
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-prod-write.sh"
}
JSONの中では、"を\"と書く必要があります。引用符が多くなりすぎるときは、スクリプトを別ファイルに分けると読みやすくなります。スクリプトには、chmod +xで実行権限を付けます。
chmod +x .claude/hooks/block-prod-write.sh
フックは、ツールの情報を標準入力(stdin)にJSONで受け取ります。たとえば、Claudeがnpm testを実行しようとしたとき、PreToolUseフックには次のようなJSONが渡されます。
{
"session_id": "abc123",
"cwd": "/home/user/my-project",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
スクリプトは、このJSONから必要な値を取り出して判断します。取り出しにはjq(JSONを扱うコマンド)がよく使われます。jqが入っていなければ、Macではbrew install jqでインストールします。たとえば実行されるコマンドの文字列は、jq -r '.tool_input.command'で取り出せます。
exit code 2 で操作を止める仕組み
フックがClaude Codeに結果を伝える方法は、終了コード(exit code)です。スクリプトの最後にexit 0やexit 2と書いた数字のことです。
| 終了コード | 意味 |
|---|---|
exit 0 | 成功。フックは異議なし(通常の権限確認は、そのまま続く) |
exit 2 | ブロッキングエラー。ツール呼び出しなどを止める。stderr(標準エラー出力)の文がClaudeに返る |
それ以外(exit 1など) | 非ブロッキングエラー。エラーの通知は出るが、操作はそのまま進む |
ここで大事なのは、止めたいときはexit 2を使うことです。公式ドキュメントにも、Claude Codeはexit 1を非ブロッキングエラーとして扱い、従来のUnixの失敗コードであっても操作を進ませる、と警告が載っています。exit 1で止めたつもりが、止まっていなかったという事故が起きます。
exit 2のとき、標準エラー出力に書いた文は、エラーメッセージとしてClaudeにフィードバックされます。たとえば、公式ドキュメントに載っている、危険なコマンドを止めるスクリプトは次のとおりです。
#!/bin/bash
# stdin から JSON 入力を読み取り、コマンドをチェック
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # ブロッキング エラー: ツール呼び出しが防止される
fi
exit 0 # 決定なし: 通常の権限フローが適用される
>&2は、文字を標準エラー出力に送る書き方です。Claudeは「Blocked: rm commands are not allowed」という文を受け取るので、理由を理解して別の方法を探せます。止めるだけでなく、理由を日本語で書いておくと、Claudeが次にやることを正しく選びやすくなります。
exit 2が何をするかは、イベントによって違います。
| イベント | exit 2で起きること |
|---|---|
PreToolUse | ツール呼び出しを止める |
UserPromptSubmit | プロンプトの処理を止めて、プロンプトを消去する |
Stop | Claudeが止まるのを防ぎ、会話を続けさせる |
SessionStart | 止められない。stderrがあなたに表示され、実行は続く |
また、exit 2のときは標準出力のJSONは無視されます。公式ドキュメントは、「exit 2でstderrを返す方法」か「exit 0でJSONを返す方法」のどちらか一方をフックごとに選ぶよう案内しています。この記事のフックは、すべて前者(exit 2)で書きます。
/hooks で設定を確認する
設定したら、Claude Codeの画面で/hooksと入力します。/hooksは、設定済みのhooksを見られる読み取り専用のメニューです。
/hooks
メニューでは、イベントの一覧と、フックを設定したイベントの横に件数が表示されます。イベントを選ぶとmatcherが、フックを選ぶと詳細(イベント・matcher・タイプ・設定元のファイル・実行するコマンド)が表示されます。各フックには、設定元を表すラベルが付きます。
| ラベル | 設定元 |
|---|---|
User | ~/.claude/settings.json |
Project | .claude/settings.json |
Local | .claude/settings.local.json |
Plugin | プラグインのhooks/hooks.json |
「設定したはずのフックが、どのファイルのものか分からない」というときは、ここで設定元を確認できます。/hooksから追加・変更・削除はできません。変更するときは、settings.jsonを直接編集するか、Claudeに「このフックを追加して」と頼みます。/hooksなどのコマンド全体は、以下の記事にまとめています。


Claude Codeのhooksで通知を出す方法(Mac・Windows)


hooksの使い方として、最初におすすめなのが通知です。AIに作業を任せている間に、ほかの作業をして、終わったら知らせてもらう。これだけで、待ち時間の使い方が変わります。
Claude Codeは、Claudeが「許可を待っているとき」や「入力を待っているとき」に、Notificationイベントを発火させます。ここに通知コマンドを登録します。
Macで通知を出す(osascript)
Macには、osascriptというコマンドがあります。これを使うと、画面の右上に通知バナーを出せます。公式ドキュメントにも載っている方法です。
まず、~/.claude/settings.jsonを開いて、次の内容を追加します。ファイルが存在しない場合は、新しく作ります。
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
"matcher": ""は、すべての種類の通知で発火するという意味です。display notificationの後の文字が通知の本文、with titleの後の文字が通知のタイトルです。本文は日本語にも変えられます。
追加したら、/hooksでNotificationに1件と表示されるかを確認します。そのあと、通知が出るかを試します。
Shift+Tabを押して、ステータスバーに⏸ manual mode onと出る状態にする- Claudeに、許可が必要な作業(ファイルの作成など)を頼む
- ターミナルから別のアプリに切り替えて、数秒待つ
許可待ちの状態が約6秒続くと、通知が出ます。
公式ドキュメントによると、osascriptの通知は、Macに組み込まれているScript Editorというアプリを通して出ます。Script Editorに通知の権限がないと、コマンドは静かに失敗して、macOSも許可を求めてきません。ターミナルでosascript -e 'display notification "test"'を1回実行して、Script Editorを通知設定に表示させます。それでも出ないときは、「システム設定」→「通知」を開き、リストのScript Editorを見つけて「通知を許可」をオンにします。
通知を「許可待ち」と「入力待ち」で出し分けたいときは、matcherにpermission_promptかidle_promptを指定します。permission_promptは、許可を求めて約6秒待っているとき、idle_promptは、応答を終えて約60秒たっても何も入力がないときです。
作業が終わったら音を鳴らす(afplay)
通知バナーに気づけない人には、音もおすすめです。Macのafplayコマンドで、音声ファイルを再生できます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
この設定は、Stopイベント(Claudeが応答を終えたとき)に、Macに標準で入っているGlass.aiffの音を鳴らします。Stopにはmatcherがないので、書かなくても大丈夫です。筆者の環境(macOS)で、鳴ることを確認しています。
音の種類を変えたいときは、同じフォルダにある別のファイルに差し替えます。ファイルの一覧は、次のコマンドで見られます。
ls /System/Library/Sounds
バナーと音を両方にしたいときは、コマンドを;でつなぎます。
{
"type": "command",
"command": "osascript -e 'display notification \"作業が終わりました\" with title \"Claude Code\"'; afplay /System/Library/Sounds/Glass.aiff"
}
NotificationとStopの違いも、ここで整理しておきます。
| イベント | 鳴るとき | 向いている使い方 |
|---|---|---|
Notification | 許可待ち(約6秒後)・入力待ち(約60秒後) | 「Claudeが待っている」ときの通知 |
Stop | Claudeが応答を終えるたび | 「作業が終わった」ときの通知・音 |
長い作業を任せるならStop、許可を求められて止まっているのに気づけないならNotification、というのが基本です。Stopは、短い応答のたびに鳴るので、うるさく感じるならNotificationのidle_promptだけに絞る方法もあります。
Windows で通知を出す
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')\""
}
]
}
]
}
}
この例は、画面の隅に出る通知ではなく、ダイアログボックス(小さなウィンドウ)を開きます。そのため、ターミナルの背後に開いてしまうことがあります。ダイアログが見えないときは、まずPowerShellで同じコマンドを直接実行して、動くかを確かめてください。WSL(Windows上のLinux環境)でClaude Codeを動かしている場合は、powershell.exeがPATHから呼び出せる必要があります。
この記事の筆者はMacで動作を確認しているため、Windowsの例は公式ドキュメントの記載の範囲の紹介にとどめています。Linuxは、公式ドキュメントにnotify-sendを使う例があります。
デスクトップアプリは設定なしで通知が出る
Claude Codeのデスクトップアプリは、hooksを設定しなくても、Macの通知が出ます。アプリに標準で備わっている純正の通知なので、この記事の設定は不要です。
この章のhooksの通知は、ターミナルで使うClaude Codeと、VS Code版向けの方法です。ターミナルやVS Codeの拡張機能には、作業の終わりを知らせる仕組みが標準では用意されていないため、hooksで補います。
VS Code版の使い方は、以下の記事で説明しています。


Claude Code hooksのおすすめ活用例7つ


ここからは、僕が実際に運用しているフックのうち、なにか問題が起きてから作ったものを7つ紹介します。
フックは、最初から「あったら便利そう」で作ると、たいてい使わなくなります。僕のフックが残っているのは、どれも「あのやらかしは二度とごめんだ」という痛い思いから作ったからです。下の7つは、事故の内容と、何を止めているかをセットで書きます。ファイル名や実際のパスは、自分の環境に合わせて読み替えてください。
7つとも、同じ型で書きます。「設定ファイルにフックを登録する」+「判断するスクリプトを書く」の2点セットです。設定は.claude/settings.json(プロジェクト用)、スクリプトは.claude/hooks/フォルダに置きます。スクリプトにはchmod +xで実行権限を付け、jqをインストールしておいてください。
本番への書き込みを止める
起きた事故:ブログ記事を、WordPress(サイトを作るシステム)へ反映する作業で、僕が手で直して公開済みだった記事を、手元の古い原稿で上書きしてしまいました。気づいたのは、読者から「内容が前に戻っている」と指摘された後でした。
止めること:決められた公開手順以外のやり方で、本番環境に書き込むコマンドを止めます。
PreToolUseのBashで、コマンドの文字列を見て判断します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-prod-write.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/block-prod-write.sh
COMMAND=$(jq -r '.tool_input.command')
# 本番への書き込みに使うコマンドが含まれていて、正規の手順(deploy.sh)を通っていなければ止める
if echo "$COMMAND" | grep -qE 'rsync|scp|wp post update' && ! echo "$COMMAND" | grep -q 'deploy.sh'; then
echo "Blocked: 本番への書き込みは ./deploy.sh を使ってください" >&2
exit 2
fi
exit 0
grep -qEの後ろのrsync|scp|wp post updateは、本番への書き込みに使う道具の名前です。自分の環境で使っているものに書き換えます。ポイントは、「何でもダメ」ではなく「正規の手順(deploy.sh)を通せば許可」にしたことです。全部を禁止すると、必要な作業まで止まってしまうからです。
Claudeには「Blocked: 本番への書き込みは ./deploy.sh を使ってください」が返るので、Claudeはコマンドを書き直して、正規の手順を使い直します。
個人情報を外へ出さない
起きた事故:記事のテンプレートに、僕の本名がそのまま混ざりかけました。匿名で運営しているのに、本名が公開されるところでした。
止めること:本名やパソコンのユーザー名が含まれるコマンド・ファイル編集を止めます。
コマンドだけでなく、ファイルの書き込みも対象にしたいので、matcherにBash|Edit|Writeを指定します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-personal-info.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/block-personal-info.sh
# ツールに渡される引数(コマンド・書き込む内容)を丸ごと1つの文字列にして調べる
INPUT=$(jq -r '.tool_input | tostring')
if echo "$INPUT" | grep -qE 'yamada-taro|山田太郎'; then
echo "Blocked: 本名またはローカルパスが含まれています。匿名の表記に直してください" >&2
exit 2
fi
exit 0
yamada-taro|山田太郎の部分に、止めたい言葉を|でつないで書きます。jq -r '.tool_input | tostring'は、ツールに渡される引数(コマンドや書き込む内容)を、全部まとめて1つの文字列にする書き方です。ツールごとに引数の項目名が違っても、1行で調べられます。
根拠のない情報の公開を止める
起きた事故:公式ドキュメントに書いていない仕様を、書いてあるかのように断定した記事を公開してしまいました。下書きの段階で誰も気づけず、公開後に見つかりました。
止めること:「事実の確認表(ファクトチェックリスト)」が未記入のまま、公開コマンドを実行しようとしたら止めます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/require-fact-check.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/require-fact-check.sh
COMMAND=$(jq -r '.tool_input.command')
# 公開コマンドのときだけ確認する
if echo "$COMMAND" | grep -q 'publish.sh'; then
# チェックリストに未チェックの項目「- [ ]」が残っていたら止める
if grep -q '\- \[ \]' fact-check.md; then
echo "Blocked: fact-check.md に未確認の項目があります。確認してから公開してください" >&2
exit 2
fi
fi
exit 0
この例では、fact-check.mdというチェックリスト(Markdownの- [ ]で書いた確認項目)に、未チェックのものが残っていたら、publish.shの実行を止めます。確認を「お願い」ではなく「通らないと進めない関門」にするのが、hooksの使い方のひとつです。
同じ記事を2本作らない
起きた事故:すでに書いてある記事と同じキーワードで、別の記事を書き始めていました。Claudeは、新しい記事を頼まれたので、新しいファイルを作っただけです。
止めること:同じ名前の記事が、すでにあるのに、新しく下書きを作ろうとしたら止めます。
PreToolUseのWrite(ファイルの新規作成)を対象にします。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-duplicate-draft.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/block-duplicate-draft.sh
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
# drafts フォルダの中の、まだ存在しないファイルを作るときだけ調べる
if [[ "$FILE_PATH" == */drafts/* && ! -e "$FILE_PATH" ]]; then
# 「24-claude-code-hooks.md」の先頭の番号を取った「claude-code-hooks.md」を取り出す
NAME=$(basename "$FILE_PATH" | sed -E 's/^[0-9]+-//')
if ls "$(dirname "$FILE_PATH")" | grep -qE "^[0-9]+-${NAME}$"; then
echo "Blocked: 同じ名前の下書きがすでにあります($NAME)。既存のファイルを確認してください" >&2
exit 2
fi
fi
exit 0
ファイル名が「番号-キーワード.md」の形式になっている前提で、番号を無視してキーワード部分が同じならば止めます。! -e "$FILE_PATH"は「そのファイルがまだ存在しない」という意味です。この条件があるので、すでにある下書きを上書きして直す作業は、止まりません。
起動時に前回の続きを読み込む
起きた事故:前回の作業の続きのつもりで話しかけたのに、Claudeは前回の内容を知らずに、別の作業を始めてしまいました。毎回「昨日はここまでやった」と説明する手間もかかっていました。
自動で行うこと:セッションを始めるたびに、「次にやること」を書いたメモを、Claudeに読み込ませます。
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "cat \"$CLAUDE_PROJECT_DIR\"/NEXT.md"
}
]
}
]
}
}
SessionStartは、標準出力に書いた文字が、Claudeの文脈(会話の前提)として追加されます。そのため、スクリプトは不要で、cat(ファイルの中身を表示するコマンド)でメモを表示するだけで済みます。matcherのstartup|resumeは、新しく起動したときと、再開したときだけ、という意味です。
読み込ませるNEXT.mdは、プロジェクトの直下に、数行で書いておきます。
## 次にやること
- 記事24のH2「通知」の下書きを確認する
- 公開前に fact-check.md の未確認項目を消す
このメモは、短く保つのがコツです。長くなると、毎回の起動で、その分だけ文脈を使ってしまうからです。
依頼ごとにルールを差し込む
起きた事故:作業ごとに守るべきルールを、別のファイルにまとめていました。ところが、記事を書く依頼のときに、Claudeがそのルールのファイルを読まずに書き始めることがありました。
自動で行うこと:依頼の内容を見て、関係するルールの場所を、毎回Claudeに添えます。
UserPromptSubmitは、あなたがプロンプトを送信した直後、Claudeが処理する前に発火します。フックは、プロンプトの文章をpromptという項目で受け取れます。
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/add-rules.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/add-rules.sh
PROMPT=$(jq -r '.prompt')
# 依頼に「記事」が含まれていたら、記事のルールの場所を添える
if echo "$PROMPT" | grep -q '記事'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "UserPromptSubmit",
additionalContext: "記事の依頼です。作業の前に rules/article.md を読んでください。"
}
}'
fi
exit 0
additionalContextに書いた文章が、Claudeの文脈に追加されます。公式ドキュメントには、additionalContextはhookSpecificOutputの内側に書かなければならず、JSONの最上位に置くと無視されると書かれています。書き間違えやすいので、この形をそのままコピーして使ってください。
なお、ここだけはexit 2ではなく、exit 0で、JSONを返す方法を使っています。先ほど説明したとおり、1つのフックでは「exit 2」か「exit 0+JSON」のどちらかに統一します。
1ターンの使用量を記録する
起きた事故:トークン(Claudeが処理する文字量の単位)を使いすぎた日がありました。ところが、どの作業で多く使ったのかが、後から分かりませんでした。
自動で行うこと:Claudeが応答を終えるたびに、時刻とセッションの情報を記録します。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "jq -r '[(now | todate), .session_id, .transcript_path] | @tsv' >> ~/.claude/turn-log.tsv"
}
]
}
]
}
}
Stopのフックには、時刻・セッションID・会話の記録ファイルのパス(transcript_path)などが渡されます。このワンライナーは、それを1行にまとめて、~/.claude/turn-log.tsvに追記します。jqのnow | todateは、現在の時刻を日時の文字に変える書き方です(公式ドキュメントの設定変更を記録する例でも使われています)。
Stopは、あなたが途中で割り込んだときには発火しません。そのため、ログは「Claudeが応答を最後まで終えた回」だけの記録になります。
トークンの使用量そのものを日別・セッション別に集計したいときは、記録したログと合わせて、ccusageが便利です。手順は以下の記事で解説しています。


Claude Code hooksの確認方法と削除・無効化


フックを設定した後の、確認・無効化・削除の方法をまとめます。
確認:/hooksで、設定済みのフックを見られます。どのイベントにいくつ設定されているか、どのファイルの設定かが分かります。
削除:settings.jsonから、そのフックのエントリを消します。
一時的に全部を無効化:設定ファイルに、次の1行を書きます。
{
"disableAllHooks": true
}
注意点は、個別のフックだけを、設定を残したまま無効にする方法はないことです。disableAllHooksは、すべてのフックをまとめて無効にします。1つだけ止めたいときは、そのエントリを消す(または別の場所に退避する)ことになります。
設定ファイルのhooksを直接編集した場合、変更は通常、自動で反映されます。数秒たっても/hooksに出てこないときは、ファイルの変更を見逃した可能性があります。セッションを再開して、読み込み直します。
フックが動かないときは、次の順で確認します。
| 症状 | 確認すること | |
|---|---|---|
/hooksに出てこない | JSONが正しいか(最後の,やコメントは書けない)/ファイルの場所が正しいか | |
| 出ているのに発火しない | イベント名とmatcherが、ツール名と合っているか(大文字小文字を区別する) | |
hook errorと出る | スクリプトが意図せずexit 0以外で終わっている。`echo ‘{“tool_name”:”Bash”,”tool_input”:{“command”:”ls”}}’ \ | ./my-hook.sh`で試す |
command not found | スクリプトのパスを絶対パスか$CLAUDE_PROJECT_DIRで書く | |
jq: command not found | jqをインストールする | |
| スクリプトが実行されない | chmod +xで実行権限を付ける |
スクリプトの動作は、フックを登録する前に、自分で1回試すのがおすすめです。ターミナルで、サンプルのJSONを渡して動かします。
echo '{"tool_name":"Bash","tool_input":{"command":"rsync -av ./ server:/var/www"}}' | ./.claude/hooks/block-prod-write.sh
echo $?
echo $?は、直前のコマンドの終了コードを表示します。止めたい入力で2、通したい入力で0が出れば、スクリプトは想定どおりです。
実行の詳細を見たいときは、2つの方法があります。
Ctrl+Oを押してトランスクリプト表示を開く:ブロックされたときのフィードバックや、エラー通知が見られるclaude --debug-file /tmp/claude.logで起動し、別のターミナルでtail -f /tmp/claude.logを実行する:どのフックがマッチして、終了コードが何だったかまで見られる。起動後に見たい場合は、セッション中に/debugを実行する
なお、フックが成功したときは、画面には何も表示されません。動いているかどうかは、通知が鳴る・ファイルが整形されるといった「効果」で確認するか、デバッグログを見てください。
ここまでで直らない、Claude Code自体が動かない、といったときは、以下の記事でエラー文ごとの切り分けを説明しています。


追加設定(プラグイン・MCP・フック)が原因かを調べるには、claude --safe-modeで起動する方法も載せています。
MCPで追加したツールの実行も、フックで止められます。MCPサーバーの追加と設定は、以下の記事でまとめています。


Claude Code hooksのベストプラクティス


最後に、約20本のフックを運用してきて分かった、フックを増やしすぎないための考え方をまとめます。
事故が起きるたびにフックを足していたら、いつの間にか約20本になっていました。すると、あるフックが別のフックの邪魔をして、肝心なときに止まってほしいフックが効かない、ということが起き始めました。今は、本当に重要なものだけに絞っています。「念のため」のフックは、増やさないのがコツです。
1. 1本目は、通知か、過去に起きた事故を止めるものにする
最初から何本も用意する必要はありません。通知を1本追加して、動くことを確かめたら、次は「実際に困った場面」を1つだけ選んで、フックにします。この記事の7つも、すべて実際の事故から作ったものです。
2. 発火する場面を絞る
matcherを省略したり"*"にしたりすると、そのイベントのたびに発火します。たとえばPreToolUseでmatcherを書かないと、ファイルを1つ読むたびにスクリプトが動きます。BashやEdit|Writeなど、必要なツールだけに絞ります。さらにif(例:"if": "Bash(git *)")を使えば、コマンドの中身が合うときだけスクリプトを起動できます。
3. フックが同時に動くことを前提にする
公式ドキュメントによると、同じイベントにマッチするフックは、並列(同時)に実行されます。いくつかの決まりがあります。
- ひとつのフックが
denyを返しても、同じイベントの別のフックの実行は止まらない(ログを書くフックは、止められた操作でも記録を残す) - 複数のフックの結果は、最も厳しい判断が優先される(
denyがallowより強い) - 複数の
PreToolUseフックが、ツールへの入力を書き換える(updatedInput)と、最後に完了したものが勝つ。フックは並列で動くので、順番は決まらない。同じツールの入力を書き換えるフックを、複数作らない
僕が「フックが干渉した」と感じたのは、まさにこの部分でした。同じ場面を対象にしたフックが複数あると、どれが効いているのか分からなくなります。
4. 止めるときの理由を、Claudeに分かる文で書く
exit 2のときの標準エラー出力は、Claudeへのフィードバックになります。「Blocked」だけでなく、「本番への書き込みは ./deploy.sh を使ってください」のように、次に何をすればいいかまで書きます。Claudeは、それを読んで、やり方を変えます。
5. exit 2を使う
止めたいときにexit 1を使うと、止まりません。ポリシーを守らせたいフックには、必ずexit 2を使います。
6. フックで書いたルールは、settings.jsonと一緒にバックアップする
~/.claude/settings.jsonとフックのスクリプトは、セットで保存します。スクリプトだけ消えると、command not foundのエラーが、毎回のツール実行のたびに出てしまいます。
7. 自動整形にはPostToolUse、ただしBash経由の書き換えは別に考える
公式ドキュメントにあるとおり、Claudeは、シェルコマンドの実行によってファイルを作成・変更することもできます。Edit|Writeだけをmatcherにしたフックでは、Bash経由で書き換えられたファイルを見逃します。すべてのファイル変更を確認したいときは、ターンごとに1回、作業フォルダ全体をチェックするStopフックを追加します。
なお、ifフィルターは「ベストエフォート」の絞り込みです。公式ドキュメントは、許可・拒否を確実に強制したい場合は、フックではなく権限システム(permissions)を使うよう案内しています。フックは「止める仕組み」として強力ですが、権限の設定と組み合わせて使うのが安全です。
権限の書き方は、以下の記事で説明しています。


まとめ:Claude Code hooksは、通知1本から始めて、事故を止めるフックへ広げる
- hooksは、Claude Codeの動きの決まったタイミング(イベント)で、コマンドを必ず実行する仕組み。CLAUDE.mdは「お願い」、hooksは「仕組み」
- 書き方は、
settings.jsonに「イベント → matcher → command」の3段で書く。止めたいときはexit 2(exit 1は止まらない) - Macの通知と音は、
NotificationやStopにosascriptとafplayを登録するだけ。デスクトップアプリは設定なしで通知が出る - 実運用のフックは、過去の事故から作る。本番書き込み・個人情報・根拠なしの公開・重複記事を
PreToolUseで止め、SessionStart・UserPromptSubmit・Stopで読み込み・添付・記録をする - 増やしすぎると、フック同士が干渉する。本当に重要なものだけに絞る
hooksは、最初、非エンジニアにとってはややとっつきにくく感じますが、最初の1つを設定するまではハードルが高く感じる機能です。でも、実際にやってみると意外とそう難しいことはありません。通知の設定なら、settings.jsonに10行ほど追加するだけで動きます。
まずは、この記事の「Macで通知を出す」の設定を、~/.claude/settings.jsonに追加してみましょう。
/hooksで1件と表示されて、通知が出たら、次は「過去に困った場面」を1つ思い出して、止めるフックを作ってみてください。
出典(Anthropic公式ドキュメント・2026年10月1日確認)
以上、ハックでした!いつも読んでくださり、ありがとうございます🙏
AI HACKSでは、AIの活用術、0からの起業・副業・最新のAIトレンドニュースなどをリアルタイムで発信しています。その他、AIで開発した無料アプリ・ツール・プロンプトテンプレートなども随時公開中です。
note (ai_hacks_jp) や、𝕏(@ai_hacks_jp)でも日々、AIの実践情報を投稿しています。







