Claude Hooks の書き方入門。settings.json から始める3例
Hooks は「特定のイベントで自動的にシェルコマンドを走らせる」しくみ——前回そう紹介しました。今回は実際に書く側に回ります。3 分で動く最小例から始めて、入力 JSON の使い方、環境変数、失敗時の直し方まで、`settings.json` をいちから組み立てていきます。
はじめに
前回の「Claude Code の Hooks とは」で、Hooks の正体——特定のイベントで harness が機械的にシェルコマンドを走らせるしくみ——をお伝えしました。今回は実際の 書き方 に進みます。
本記事のゴールは、
~/.claude/settings.jsonがどこにあるか分かる- 最小の Hook が手元で動かせる
- 3つの実例(編集後の git diff/危険コマンドのブロック/応答終了の通知)が書ける
- うまく動かないときの直し方が分かる
の4点です。最初の Hook が動くまで の最短手順を、いちから歩きます。
settings.json の場所
Hooks の設定は settings.json という JSON ファイルに書きます。場所は3つあります。
| スコープ | 場所 | 適用範囲 |
|---|---|---|
| User(あなた個人) | ~/.claude/settings.json | 全プロジェクト |
| Project(案件共通) | <案件フォルダ>/.claude/settings.json | その案件、Git で共有 |
| Local(このマシン専用) | <案件フォルダ>/.claude/settings.local.json | その案件、git に上げない |
最初に練習するなら User の ~/.claude/settings.json がおすすめです。1か所書き換えれば、すべてのプロジェクトで効くので、動作確認が楽です。
ファイルが存在しなければ、新規作成して構いません。空の状態から、
{}
の1行があれば最低限 OK です。
最小例:編集後に git diff --stat を表示する
最初の Hook として、「ファイルを編集するたびに git diff --stat を表示する」 という素朴なものを書きます。
~/.claude/settings.json に次を書き込みます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "git diff --stat HEAD"
}
]
}
]
}
}
これだけで、Claude Code を 再起動 すると Hook が有効になります。Claude が Edit か Write でファイルを書き換えるたびに、git diff --stat HEAD が走り、変更ファイルの一覧が会話に出てきます。
この JSON の構造を読む
短いですが、Hooks の構造はこの形がベースになります。
hooks ← Hooks 全体を入れる箱
└── PostToolUse ← イベント種別
└── 配列 ← 同じイベントに複数のグループを書ける
└── matcher ← どのツールに反応するか(正規表現可)
└── hooks ← 実行するコマンドの配列
└── type ← 種別("command" が基本)
└── command ← 実行するシェルコマンド
入れ子が深く見えますが、中身は「イベント → 対象ツール → 実行コマンド」の3段 だけです。慣れると一瞬で書けるようになります。
matcher の書き方
matcher には どのツール名のときに発火するか を正規表現で書けます。
| matcher | 意味 |
|---|---|
"Edit" | Edit ツールのときだけ |
| `“Edit | Write”` |
"Bash" | Bash ツールのとき |
""(空) | すべてのツールで発火 |
".*" | 同上(すべて) |
最初は 対象を絞っておく のが安全です。"" で全ツールに反応させると、想定外のところで Hook が走り、動作が読みづらくなります。
よく使うツール名
Claude Code でよく出てくるツール名は、
Edit/Write:ファイルの編集/新規作成Read:ファイルの読み取りBash:シェルコマンドの実行Grep/Glob:検索系
このあたりです。「ファイルが書き換わったときに何かしたい」なら Edit|Write、「コマンドが走る前に確認したい」なら Bash を使う、というのが定石になります。
Hook には「入力 JSON」が来る
Hook のコマンドが起動されるとき、標準入力(stdin)に JSON が流れ込んできます。中身には「何のイベントか」「どのツールが何の引数で呼ばれたか」が入っています。
たとえば PostToolUse(Edit) で発火した Hook の標準入力には、
{
"session_id": "abc123",
"hook_event_name": "PostToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "/Users/me/projects/site/src/index.ts",
"old_string": "...",
"new_string": "..."
},
"tool_response": { ... }
}
のような JSON が渡されます。Hook のコマンド側でこの JSON を読めば、「どのファイルが書き換わったか」「中身はどうなったか」が手に入ります。
JSON を使う最小例
たとえば「書き換わったファイルが .ts だったら、そのファイルだけ型チェックする」という Hook はこう書けます(jq というJSON処理ツールを使う前提)。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path'); if [[ \"$FILE\" == *.ts ]]; then npx tsc --noEmit \"$FILE\"; fi"
}
]
}
]
}
}
中身を分解すると、
| 部分 | 意味 |
|---|---|
FILE=$(jq -r '.tool_input.file_path') | 標準入力の JSON から file_path を抜き出して FILE に入れる |
if [[ "$FILE" == *.ts ]]; then ... fi | FILE が .ts で終わっていれば中の処理を走らせる |
npx tsc --noEmit "$FILE" | そのファイルだけ型チェック |
シェルスクリプトが書ければ、Hook はかなり自由にカスタマイズできます。jq は事前に brew install jq(Mac の場合)で入れておく必要があります。
環境変数も使える
Hook のコマンドの中では、いくつかの 便利な環境変数 が使えます。
| 環境変数 | 意味 |
|---|---|
$CLAUDE_PROJECT_DIR | いま Claude Code が動いているプロジェクトのフォルダ |
$HOME | あなたのホームフォルダ(OS 標準) |
たとえば「プロジェクトのルートに置いてあるスクリプトを呼ぶ」なら、
{
"command": "$CLAUDE_PROJECT_DIR/scripts/post-edit.sh"
}
と書けます。プロジェクトごとに動きを変えたいときに重宝します。
実例 1:危険コマンドをブロック
PreToolUse(Bash) で、Claude が実行しようとしているコマンドをチェックして、rm -rf が含まれていたら止める という Hook を書いてみます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(jq -r '.tool_input.command'); if echo \"$CMD\" | grep -qE 'rm\\s+-rf'; then echo 'Blocked: rm -rf is not allowed'; exit 2; fi"
}
]
}
]
}
}
ポイントは末尾の exit 2 です。PreToolUse の Hook が exit 2 で終わると、harness は 「このツール実行をブロック」 と解釈します。Claude には「Hook によってブロックされた」と伝わり、別の方法を考えてくれます。
| 終了コード | 意味 |
|---|---|
0 | 正常終了。普通に実行を続ける |
2 | ブロック。PreToolUse で使うとツール実行を止める |
| その他 | エラー扱い。stderr の内容が会話に出る |
「取り返しのつかないコマンドだけは止める」という保険は、特に Bypass Permissions モードで Claude を走らせるときの命綱になります。
実例 2:応答終了で通知を出す(macOS)
長く走らせていて別作業をしているとき、Claude の応答が終わったら macOS の通知センターに通知 を出す Hook です。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"応答が終わりました\" with title \"Claude Code\"'"
}
]
}
]
}
}
Stop イベントは Claude が応答を終えたタイミングで発火します。matcher を省略しているのは、Stop には対象ツールがないためです。
長いプロンプトを投げて別作業に移るときに、これを入れておくと 「終わったかどうかをわざわざ画面で確認しない」 で済むようになります。
失敗パターンと対処
Hooks を書き始めると、よく踏むパターンがあります。先回りで対処を書いておきます。
失敗 1. クォートが甘くて壊れる
JSON の中にシェルコマンドを書くので、クォートのエスケープ がやっかいです。
× "command": "echo "hello"" ← JSON が壊れる
○ "command": "echo \"hello\"" ← OK
○ "command": "echo 'hello'" ← シングルクォートなら OK
複雑なコマンドは、シェルスクリプトファイルに切り出して $CLAUDE_PROJECT_DIR/scripts/foo.sh を呼ぶ ようにすると、クォート問題から解放されます。
失敗 2. ツール名のスペル違い
matcher の "Edit" を "edit" と小文字で書くと反応しません。ツール名は大文字小文字を厳密に区別 します。/help で確認できる正式名で書いてください。
失敗 3. 設定ファイルの JSON 構文ミス
カンマの過不足、波括弧の閉じ忘れなど。JSON は厳しいので、書いたら必ず構文チェック することをおすすめします。
cat ~/.claude/settings.json | jq .
jq . を通して何も出なければ構文エラー、整形された JSON が返ってくれば OK です。
失敗 4. Claude Code の再起動を忘れる
settings.json は Claude Code の起動時に読まれます。ファイルを書き換えただけでは、いま動いているセッションには反映されません。書き換えたら必ず Claude Code を起動し直す が鉄則です。
失敗 5. 標準出力に余計なものが出る
Hook のコマンドが標準出力に余計な情報を出すと、それが会話に表示されて鬱陶しいです。ログを残したいなら console.error 相当(stderr)か、ログファイルへリダイレクト するのが定石。
{
"command": "your-command >> ~/.claude/hook.log 2>&1"
}
デバッグの3点セット
それでも動かないとき、頼れる確認手段は3つです。
1. コマンドを単独で動かしてみる
settings.json に書いたコマンドを、ターミナルで 直接実行してみる。
echo '{"tool_input":{"file_path":"test.ts"}}' | <your-command>
JSON を echo で渡しながら動かすと、Hook の中で動いていることを再現できます。
2. ログファイルに記録する
{
"command": "echo \"$(date) hook fired\" >> ~/.claude/hook.log"
}
シンプルにログを取って、ファイルが増えるか確認します。増えなければそもそも発火していない、増えていればコマンドのどこかで失敗している、と切り分けできます。
3. /permissions でブロックされていないか確認
権限設定で Hook の動作を絞っているケースもあります。/permissions で現状を確認してみてください。
触り始めるときの順序(再掲)
前回の概念編にも書きましたが、Hooks は 慎重に始める のが結果的に早道です。
PostToolUseで「結果を表示するだけ」の Hook(git diff --statなど)- 慣れたら
Stopで通知 - その次に
PreToolUseでブロック系 - 最後に
UserPromptSubmitでログ取得や定型挿入
副作用のある Hook は 後回し に。「想定外の場面で発火して困った」が一番ありえる失敗なので、最初の数日は読むだけ系でクセを掴むのが安全です。
まとめ
Hooks 基本の書き方の要点を整理します。
- 書き場所は
~/.claude/settings.json(または案件配下) - 構造は「イベント → matcher → command」の3段
- 最小例:
PostToolUse(Edit|Write)でgit diff --statを出す - matcher はツール名の正規表現:絞らないと想定外で発火する
- 標準入力に JSON が来る:
jqで抜き出して使える $CLAUDE_PROJECT_DIRなどの環境変数が使えるPreToolUseでexit 2するとツール実行をブロックStop通知で長時間ジョブの完了を物理的に知る- 失敗5パターン:クォート/ツール名/JSON構文/再起動忘れ/標準出力の汚染
- デバッグは「単独実行」「ログファイル」「
/permissions」の3点
これで Hooks シリーズの2本(とは/書き方)が読み終わりました。Skills が「知識」、MCP が「外部接続」、Hooks が「タイミング」——と、Claude Code の 3軸の拡張機構 がひと通り出揃いました。
次の記事「Claude Code の SubAgent とは」では、Step 3 の最後のテーマ SubAgent(サブエージェント) に進みます。SubAgent は「メイン会話のコンテキストを汚さず、別働隊として独立した Claude を走らせるしくみ」で、重い調査やコードベースの大掃除を任せるのに向いています。続けて読むと、Claude Code の使いこなしが「1人の Claude と話す」から「複数の Claude を采配する」段階に進みます。
WordPressを実際に動かしてきたサーバー:ロリポップ
Claude Code でWordPressサイトを組み立てるとき、最初に置く先として無理のないレンタルサーバー。月数百円から始められ、WordPressの自動インストールにも対応しています。設定で詰まりがちな初期段階の時間をかなり減らせます。