Hooks
05. CLAUDE.md とメモリ と 06. Skills で、CLAUDE.md やスキルに書いた指示は、Claude が読んで「従おうとする」もので、必ず守られるとは限らない、と書きました。フックは、その反対の性質を持つ仕組みです。決まったタイミングで、決まったコマンドを、Claude の判断を通らずに、必ず実行します。このページでは、フックの仕組みと使い道を整理し、「Claude が入力を待っているときに、Mac の通知を出す」フックを、実際に作って試します。
この記事で分かること
- フックとは何か。誰が提供し、誰が実行するのか
- フックが動く場所と、動かせるタイミング
- フックで何ができるか。権限の仕組みとの関係
- 入力待ちの通知のフックを作って、ターミナル版とデスクトップアプリで試した結果
用語の整理
| 用語 | 意味 |
|---|---|
| フック(hook) | Claude Code の中で何かが起きたときに、決めておいたコマンドを自動で実行する仕組み |
| イベント(event) | フックが動くきっかけになる出来事。「道具を使う直前」「Claude が入力を待っている」など |
| マッチャー(matcher) | イベントの中で、さらにどの場合に動かすかを絞り込む条件。たとえば「道具の名前が Bash のときだけ」 |
| 通知(notification) | 画面の右上に出る、Mac の知らせ |
フックとは
提供するのも、実行するのも、Claude Code
フックは、Claude Code の機能の 1 つです。何を実行するかは、利用者やプロジェクト、組織の管理者が、設定ファイルに書きます。書いたコマンドを、決まったタイミングで実行するのは、Claude(モデル)ではなく、Claude Code の本体です。
| CLAUDE.md、スキル | フック | |
|---|---|---|
| 誰が扱うか | Claude が読んで、従おうとする | Claude Code の本体が、実行する |
| 必ず守られるか | 守られないことがある | 決まったタイミングで、必ず実行される |
| 向いていること | 方針や、手順の説明 | 毎回、必ず行わせたいこと |
同じ名前の、別の仕組み
「フック」という言葉は、ほかの道具でも使われています。
| 名前 | 提供しているところ | 動くタイミングの例 |
|---|---|---|
| Claude Code のフック(このページで扱うもの) | Claude Code | Claude が道具を使う前後、Claude が入力を待っているとき など |
| Git のフック | Git | コミットの前、push の前 など。Claude を使っていなくても、人がコミットしたときにも動く |
| GitHub の Webhook | GitHub | プルリクエストが作られたとき など。別のサーバーに知らせを送る |
考え方は似ていますが、別々の仕組みです。Claude Code のフックは、Claude Code の中での出来事にだけ反応します。
フックが動く場所
公式ドキュメントには、フックの仕組みは、ターミナル版、エディターの拡張機能、デスクトップアプリ、クラウドのセッションの、どこで Claude Code を使っても同じように働く、と書かれています。
| Claude Code が動いている場所 | フックのコマンドが動く場所 |
|---|---|
| ターミナル版、デスクトップアプリ、VS Code の拡張機能(自分の PC での会話) | 自分の PC |
| クラウドのセッション | クラウドの環境の中。自分の PC の ~/.claude/settings.json は読まれない |
自分の PC で動く場合、公式ドキュメントは、次のように警告しています。
Command hooks execute shell commands with your full user permissions. (コマンドのフックは、利用者のすべての権限で、シェルのコマンドを実行する)
フックに書いたコマンドは、利用者が自分でターミナルに入力したのと同じ力を持ちます。03. 権限モードと、確認を減らす方法 で見た権限の確認も、通りません。自分で中身を理解したコマンドだけを書きます。ほかの人のリポジトリの .claude/settings.json にフックが書かれていたら、中身を確かめてから使います。
フックを動かせるタイミング
フックを動かせるイベントは、数多くあります。主なものを挙げます。
| イベント | 動くタイミング |
|---|---|
SessionStart | 会話が始まったとき、再開したとき |
UserPromptSubmit | 利用者が依頼を送って、Claude が読む前 |
PreToolUse | Claude が道具を使う直前。止めることもできる |
PermissionRequest | Claude Code が、利用者に許可を求めようとしたとき |
PostToolUse | Claude が道具を使い終わった直後 |
Notification | Claude Code が、利用者に知らせを出すとき(許可を待っている、入力を待っている、など) |
Stop | Claude が答え終わったとき |
FileChanged | 指定したファイルが、変更されたとき |
ConfigChange | 会話の途中で、設定ファイルが変わったとき |
Claude のファイルの読み書きを、監視できるか
フックで、ファイルの読み書きを監視することもできます。ただし、範囲があります。
| 知りたいこと | フックでできるか |
|---|---|
| Claude が、道具でどのファイルを読み書きしたか | できる。道具を使う前後(PreToolUse、PostToolUse)のフックで記録できる。直前のフックなら、止めることもできる |
| 指定したファイルが、誰かに変更されたか | できる(FileChanged)。Claude 以外のアプリが変えた場合も分かる。ただし、作業フォルダーの中の、名前を指定したファイルだけで、分かるのは変更だけ |
| Claude が動かしたプログラムの中で、どのファイルが読まれたか | できない。フックに渡るのは、実行したコマンドの文字だけ |
| PC 全体のファイルの読み書き | できない。フックは、PC 全体を見張る道具ではない |
フックで何ができるか
公式ドキュメントの使い方の手引きには、次の使用例が挙げられています。
| 使用例 | 使うイベント | 内容 |
|---|---|---|
| 入力待ちを通知する | Notification | Claude が入力や許可を待っているときに、Mac の通知を出す |
| 編集のあとに、自動で整形する | PostToolUse | Claude が編集したファイルに、書式を整える道具を自動で実行する |
| 大事なファイルの編集を止める | PreToolUse | .env などの編集を止める。止めた理由が Claude に伝わる |
| 会話の要約のあとに、大事な情報を入れ直す | SessionStart | 会話が要約されたあとに、プロジェクトの決まりを Claude に伝え直す |
| 設定ファイルの変更を記録する | ConfigChange | 会話の途中での設定の変更を、記録に残す |
| 特定の確認を、自動で許可する | PermissionRequest | 決めた種類の確認だけ、利用者の代わりに「許可」と答える |
止める力は強く、許す力には限りがある
公式ドキュメントには、権限の仕組みとの関係が、次のように書かれています。
- 道具を使う直前のフックで「拒否」と答えると、権限モードが何であっても止まる。確認をすべて省くモードでも止まる。利用者がモードを変えても抜けられない決まりを作れる。
- フックで「許可」と答えても、設定ファイルの禁止のルールは破れない。組織が「確認する」と決めた道具の確認なども、省けない。
フックの書き方
フックは、04. 設定ファイルの仕組み で扱った設定ファイルに、hooks という項目で書きます。
{
"hooks": {
"イベントの名前": [
{
"matcher": "絞り込みの条件",
"hooks": [
{ "type": "command", "command": "実行するコマンド" }
]
}
]
}
}フックを書く場所によって、効く範囲が決まります。考え方は、ほかの設定と同じです。
| 書く場所 | 効く範囲 | ほかの人に届くか |
|---|---|---|
~/.claude/settings.json | 自分の、すべてのプロジェクト | 届かない |
.claude/settings.json | そのプロジェクト | 届く。コミットすれば |
.claude/settings.local.json | そのプロジェクトの、自分だけ | 届かない |
| 管理設定 | 組織の全員 | 届く。管理者が配る |
フックに渡される情報は、標準入力から、JSON の形で受け取れます。たとえば、道具を使い終わった直後のフックには、使った道具の名前、入力(実行したコマンドなど)、結果などが渡されます。
実際にやってみる:入力待ちを通知する
Claude に長い作業を任せている間に、別のことをしていると、Claude が確認や入力を待っていることに気づかないことがあります。そこで、公式ドキュメントの使用例にある「入力待ちを通知する」フックを作り、試しました。作ったもの、準備したこと、試したこと、確かめずに区切ったこと、の順に書きます。
1. 作ったフック
~/.claude/settings.json に、次のフックを書きました。通知は個人の好みなので、プロジェクトの設定ではなく、自分の設定に書いています。
{
"theme": "dark",
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude が入力を待っています\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
]
}
}| 部分 | 意味 |
|---|---|
Notification | Claude Code が、利用者に知らせを出すときに動く |
"matcher": "" | 知らせの種類を問わず、すべてで動く |
command | AppleScript で、Mac の通知を出す。公式ドキュメントの例の本文を日本語にし、音(Glass)を加えた |
公式ドキュメントによると、Notification のフックが動く主な場面は、次の 2 つです。
| 場面 | 動くタイミング |
|---|---|
許可を待っている(permission_prompt) | 確認の画面が出てから、約 6 秒たっても答えがないとき |
入力を待っている(idle_prompt) | Claude が答え終わってから、約 60 秒たっても入力がないとき |
~/.claude/settings.json は、最初からありました。ターミナル版を初めて起動したときに、画面の色合い(テーマ)を選んだので、そのときに作られたものです。中身は "theme": "dark" の 1 行だけでした。この 1 行を残したまま、フックを書き足しています。
2. 準備:通知が画面に出るようにする
フックを書く前に、通知のコマンドだけを試しました。ここで、2 つのことが分かりました。
通知の許可と、表示のしかた
AppleScript の display notification で出す通知は、macOS では「スクリプトエディタ」から出たものとして扱われます。公式ドキュメントにも、スクリプトエディタに通知の許可がないと、何も表示されず、エラーも出ない、と書かれています。
この Mac では、許可はすでにオンでした。ただし、通知の表示のしかたが「一時的」になっていて、数秒で消えてしまいました。「システム設定」→「通知」→「スクリプトエディタ」で、表示のしかたを「持続的」に変えると、自分で閉じるまで残るようになりました。
Claude のコマンドの環境からは、通知が出ない
Claude が、自分のコマンドの道具(Bash)で、通知のコマンドを直接実行しても、通知は画面に出ませんでした。コマンドはエラーなく終わります。同じコマンドを、利用者がターミナルで実行すると、通知が出ました。Claude のコマンドを OS の仕組みで囲うサンドボックス(03. 権限モードと、確認を減らす方法)は、このとき有効になっていなかったので、それが原因ではありません。理由は分かっていません。
3. 試したことと、結果
フックを設定したあと、ターミナル版とデスクトップアプリで試しました。
| 試したこと | 使う場所 | 結果 |
|---|---|---|
| 答え終わってから、約 60 秒待つ | ターミナル版 | 通知が出た |
| 答え終わってから、約 60 秒待つ | デスクトップアプリ | 通知が出なかった(2 回試して、2 回とも) |
| 確認の画面に答えずに、約 10 秒待つ(権限モードを一時的に Manual にした) | デスクトップアプリ | 通知が出なかった |
デスクトップアプリで出なかった理由を調べるため、フックのコマンドに、「動いた時刻と、渡された情報を、ファイルに書き残す」処理を加えました。
| 試したこと | 結果 | 分かること |
|---|---|---|
| デスクトップアプリで、答え終わってから約 60 秒待つ(記録の処理を加えて、2 回) | 記録のファイルが、作られなかった | Notification のフックが、動いていない |
| デスクトップアプリを背面にして、答え終わってから約 60 秒待つ(記録の処理を加えて、1 回) | 記録のファイルが、作られなかった | アプリが背面にあっても、動かない |
| 記録の処理そのものを、Claude が直接実行する | 記録のファイルが作られ、正しく書き残された | 記録の処理には、問題がない |
さらに、デスクトップアプリで、フックそのものが動くかどうかを、別のイベントで確かめました。道具を使い終わった直後(PostToolUse)のフックに、記録と通知の処理を書き、Claude がコマンドを実行しました。
| 試したこと | 結果 |
|---|---|
| 道具を使い終わった直後のフックで、記録を書く | 記録が残った。使った道具の名前、実行したコマンド、作業フォルダー、権限モードなどが、フックに渡されていた |
| 同じフックで、通知を出す(題名「study-lab のフック」、本文「フックからの通知(テスト)」。ほかの通知と区別できる文面にした) | 通知が出た |
このフックは、会話の途中で設定ファイルに書き足したものですが、会話を始め直さなくても、すぐに動きました。
ここまでで分かったことを、まとめます。
| 確かめたこと | 結果 |
|---|---|
| デスクトップアプリで、フックは動くか | 動く |
| デスクトップアプリで動いたフックから、Mac の通知を出せるか | 出せる |
デスクトップアプリで、答え終わって約 60 秒の Notification のフックは動くか | 動かなかった |
ターミナル版で、答え終わって約 60 秒の Notification のフックは動くか | 動いた |
答え終わって約 60 秒のきっかけ(idle_prompt)について、公式ドキュメントの説明は、ターミナルでの会話を前提に書かれています。デスクトップアプリで起きるかどうかは、書かれていませんでした。
確かめ終わったあと、道具を使い終わった直後のテスト用のフックと、記録の処理は外しました。Notification のフックは、最初の形のまま残しています。
4. 確かめずに区切ったこと
デスクトップアプリでの入力待ちの通知は、次の 2 点を確かめないまま、ここで区切りにしました。普段はデスクトップアプリを Auto で使っていて確認の画面がめったに出ないこと、代わりに作った記録のフック(下の「実際にやってみる:Claude が触ったファイルを記録する」)が実用に足りていることが理由です。
| きっかけ | 公式ドキュメントに書かれていること | このサイトで分かっていること |
|---|---|---|
確認の画面が出て約 6 秒(permission_prompt) | デスクトップアプリでも、確認を求めてから約 6 秒で動く(v2.1.233 から)。ターミナル版と違い、入力中でも遅れない。約 6 秒より前に答えると、動かない | 試したときは通知が出なかった。ただし、記録の処理を加える前だったので、フックが動いたかどうかは分からない |
答え終わって約 60 秒(idle_prompt) | 約 60 秒で動くという説明は、ターミナルでの会話についてのもの。デスクトップアプリでの動きとして書かれているのは、permission_prompt だけ | 前面で 2 回、背面で 1 回試して、3 回とも動かなかった。理由は分かっていない |
実際にやってみる:Claude が触ったファイルを記録する
入力待ちの通知は、デスクトップアプリでは、きっかけが起きませんでした。一方で、道具を使い終わった直後(PostToolUse)のフックは、デスクトップアプリでも動くことを確かめました。そこで、このイベントを使って、「Claude が、いつ、どのファイルに触れたか」を記録するフックを作りました。上の「Claude のファイルの読み書きを、監視できるか」で整理した内容を、実際に確かめるものです。作ったもの、試したこと、分かったこと、の順に書きます。
1. 作ったもの
記録の処理
フックから渡された情報から、必要な項目だけを取り出して、1 件 1 行で記録のファイルに書き足す処理を、~/.claude/hooks/log-tool-use.sh に書きました。情報の取り出しには、JSON を扱うコマンド jq を使っています(この Mac には、最初から入っていました)。
#!/bin/sh
# Claude Code の PostToolUse フックから呼ばれる。
# 時刻、使う場所、セッション、道具の名前、触れたファイルの場所(またはコマンドの文字)を、1 件 1 行で記録する。
# ファイルの中身や、コマンドの結果は記録しない。
jq -r --arg entry "${CLAUDE_CODE_ENTRYPOINT:-unknown}" '
def oneline: tostring | gsub("\n"; " ⏎ ");
[ (now | localtime | strftime("%Y-%m-%d %H:%M:%S")),
$entry,
((.session_id // "") | .[0:8]),
.tool_name,
( if .tool_name == "Bash" then (.tool_input.command // "")
elif (.tool_name == "Grep" or .tool_name == "Glob")
then ((.tool_input.path // ".") + " " + (.tool_input.pattern // ""))
else (.tool_input.file_path // "") end | oneline ),
( if .tool_input.description then "(" + (.tool_input.description | oneline) + ")" else empty end )
] | join(" ")
' >> "$HOME/.claude/tool-use.log"記録する項目と、記録しない項目は、次のように決めました。
| 記録する | 記録しない |
|---|---|
| 時刻 | ファイルの中身 |
| 使う場所(デスクトップアプリか、ターミナル版か) | コマンドの結果 |
| セッション(識別番号の先頭 8 文字) | |
| 道具の名前 | |
| 触れたファイルの場所。コマンドの場合は、コマンドの文字と、Claude が付けた説明 |
ファイルの中身やコマンドの結果を記録しないのは、記録が大きくなることと、記録のファイルに、読んだファイルの中身が残ってしまうことを避けるためです。
使う場所は、Claude Code が設定する環境変数(CLAUDE_CODE_ENTRYPOINT)から、セッションは、フックに渡される情報(session_id)から取っています。
フックの設定
このフックは、まずこのサイトの作業だけで試すために、プロジェクトの、自分だけの設定(.claude/settings.local.json)に書きました。このファイルは、Git の管理の対象外で、公開されません。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Read|Write|Edit|Grep|Glob|Bash",
"hooks": [
{ "type": "command", "command": "\"$HOME/.claude/hooks/log-tool-use.sh\"" }
]
}
]
}
}matcher で、ファイルを読む(Read)、書く(Write)、編集する(Edit)、探す(Grep、Glob)道具と、コマンドの実行(Bash)に絞っています。
記録の処理は、フックに組み込む前に、作りものの情報(ファイルを読む、複数行のコマンド、ファイルを探す)を渡して、正しい形で書かれることを確かめました。
2. 試したこと
このプロジェクトで始めた、3 つのセッションで試しました。どのセッションの記録かを見分けられるように、それぞれ、違うファイルについて尋ねました。
| セッション | 頼んだこと |
|---|---|
| デスクトップアプリの、このページを書いているセッション | CLAUDE.md を読む。Python のコマンドで README.md の 1 行目を表示する |
| ターミナル版の、新しいセッション | README.md の 1 行目を教えて |
| デスクトップアプリの、別の新しいセッション | package.json の name を教えて |
3 つとも、記録が残りました。記録の例です(ホームフォルダーの場所は ~ に置き換えています)。
2026-10-18 11:02:52 claude-desktop 344a479d Bash tail -n 2 ~/.claude/tool-use.log | ... (Show the newest log lines ...)
2026-10-18 11:08:19 cli 60322502 Bash ls README* 2>/dev/null (Find README files)
2026-10-18 11:08:20 cli 60322502 Read ~/python-projects/study-lab/README.md| 項目 | 読み方 |
|---|---|
claude-desktop、cli | 使う場所。デスクトップアプリは claude-desktop、ターミナル版は cli と記録された |
344a479d、60322502 | セッション。セッションごとに、別の値になった |
なお、最初は、使う場所とセッションを記録していませんでした。3 つのセッションの記録が混ざって、どれがどのセッションのものかを、頼んだファイルの名前と時刻から見分ける必要がありました。そこで、この 2 つの項目を足しました。
3. 分かったこと
| 確かめたこと | 結果 |
|---|---|
| プロジェクトの設定に書いたフックは、どのセッションで動くか | このプロジェクトで始めた、デスクトップアプリの別のセッションでも、ターミナル版のセッションでも動いた |
| フックを、会話の途中で書き足したとき | 会話を始め直さなくても、すぐに記録が始まった |
| 1 つの依頼で、何件の記録になるか | 依頼によって違う。ターミナル版の Claude は、先にコマンドで README のファイルを探し、そのあとで読む道具で読んだ。1 つの依頼が、2 件の記録になった |
| 同じ依頼でも、使う道具は変わるか | 変わる。ターミナル版の Claude は、読む道具で README.md を読み、デスクトップアプリの別のセッションの Claude は、コマンド(grep)で package.json を調べた。どちらも記録された |
記録の限界も、実際に確かめられました。Python のコマンドで README.md を読んだときの記録は、次のとおりです。
2026-10-18 10:47:50 Bash venv/bin/python -c "print(open('README.md').read().splitlines()[0])" (Print the first line of the README using a Python one-liner)記録の上では、道具は Bash で、README.md を読んだことは、コマンドの文字を読み解かないと分かりません。別のファイルに書いたプログラムを動かした場合は、何を読んだのかは、記録からは分かりません。
このプロジェクト以外で始めたセッションや、クラウドのセッションでは、このフックは動きません。設定を、プロジェクトの設定に書いているためです。すべてのプロジェクトで記録したい場合は、利用者の設定(~/.claude/settings.json)に書きます。
まとめ
- フックは、Claude Code の本体が、決まったタイミングで、決まったコマンドを必ず実行する仕組み。Claude の判断を通らない。CLAUDE.md やスキルと違って、必ず実行される。
- 自分の PC での会話では、フックのコマンドは、自分の PC の上で、利用者のすべての権限で動く。中身を理解したコマンドだけを書く。
- 道具を使う直前のフックは、権限モードに関係なく、操作を止められる。フックで許可しても、禁止のルールは破れない。
- 入力待ちの通知のフックは、ターミナル版では、答え終わって約 60 秒で動いた。デスクトップアプリでは、同じきっかけでは動かなかった。ただし、デスクトップアプリでも、フックの仕組みそのものは動き、フックから通知も出せた。
- 道具を使い終わった直後のフックで、Claude が触ったファイルとコマンドを記録できた。プロジェクトの設定に書くと、そのプロジェクトで始めた、デスクトップアプリとターミナル版のどのセッションでも動いた。ただし、コマンドで動かしたプログラムの中で読み書きされたファイルは、記録に残らない。
確認の記録
| 項目 | 内容 |
|---|---|
| 最終確認日 | 2026 年 10 月 18 日(「4. 確かめずに区切ったこと」は 2026 年 10 月 24 日) |
| Claude Code のバージョン | デスクトップアプリ 2.1.295、ターミナル版 2.1.300(macOS) |
| 検証の状況 | 2 つの「実際にやってみる」は、この Mac で、ターミナル版とデスクトップアプリを使って確かめた結果。それ以外の節は、公式ドキュメントの内容をもとに書いている |
| 確かめていない内容 | 入力待ちの通知の「4. 確かめずに区切ったこと」の 2 点。入力待ちの通知と、ファイルの記録以外の使用例(整形、編集を止める、自動で許可する など)は、実際には試していない |