Claude Code Hooks、便利そうと思っても億劫になって今までゴリゴリ設定していませんでした。新しいプロジェクトになったら大部分を設定し直さなきゃいけないと思うとやる気にはなれませんでした。通知設定だけはしていました。(音声+バナー+効果音はとても良いです。おすすめです。)
今回機会があったので、これを利用してHooksでできることをたくさん理解していきたいと思います。公式ドキュメントを見つつ、運用を考えてみました。個人開発です。そのレポートみたいなものです。
以下の記事を書いた後に少し個人開発をしました。感想です。
- 今回設定した中で開発中に関与するのはおもにLinterとFormatterだけでした。
- 一つ、設定ファイルでファイルパスを間違えていたところがあったのでそれを修正してもらったのですが、その時にちゃんと通知が出ました。
- 私はまだかなり高頻度で手動のallow/denyをしているので、4でやったブロックが発動したことはまだありませんでした。
- その他もお守り的につけてあるので今後経過観察して気づいたことがあれば書き足していこうと思います。
1. プロジェクト毎運用は大して面倒じゃなかった
なんとなくできる気はしていましたが確認していなかったことです。Claude CodeはSkillsでupdate-configというのがあるのでそれを利用すれば自然言語で設定を変更することが可能です。つまり、もしプロジェクト毎に細かい設定が必要な場合はそれ自体をプロンプト化すればよいということです。2,3のformatterとlinterはその一例です。
その他の共通部分はユーザー設定に置いておきます。量が多くて頭も使うので気が引けますが、一度やれば開発体験がさらに良くなると考えました。
↓このように、わざわざ言ってみても全く変化がないので、LinterとFormatterが適切に動いているであろうことが確認できた。

2. Formatter
自分が扱う言語のコードフォーマッタをあらかじめ選定しておきます。私はそれほど開発経験がある方ではないので、コードフォーマッタの比較は省略し、評判が良さそうなものを選びます。今回はPythonのRuffのblack modeを選び、Claude Codeに伝えました。
3. Linter
Formatterと同様にコードの可読性を高めるのに効果的なので入れます。PythonにRuffを使います。
参考:編集後にコードを自動フォーマットする - Claude Code Docs
4. 保護されたファイルへの編集をブロックする
Claude Codeでは設定ファイルのpermissions.denyの他に、Hooksでブロックすることもできます。なぜあえてこちらでやるのかというと、ブロックされるのはわかるが、本当にそれを見たいときに適当な代替案を考えさせてしまうのを防ぐことができるから...というのが本来の意図と思われます。
たとえば、無理やり読もうとしてエラーを何度も繰り返したりしてしまうことを防ぐことができます。(私はまだ出くわしたことがありませんが...)
.env.localなどは説明不要ですから、permissions.denyにグローバル設定に入れておくほうが良い....
と思っていたのですが、今(2026/03/25)は不具合が起きていて、permissions.denyが適用されない?というようなissueが開いています。permissions.denyよりもHooksによる保護もやっておいたほうが良いと思います。
ドキュメントからの変更点は、PreToolUseをmatcher: Read|Write|Edit|Bashに対して設定してあります(""にしておく)。また、グローバル設定にし、Bash用の処理を加えてあります。
ただし、cat .e''nv や cat $DOTENV_PATH は検知できないので、限界はあることに注意してください。
~/.claude/hooks/protect-files.sh
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
BASH_CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
if [[ "$BASH_CMD" == *"$pattern"* ]]; then
echo "Blocked bash command: touches '$pattern'" >&2
exit 2
fi
done
exit 0
その後、chmodします。(macOS/Linux)
chmod +x ~/.claude/hooks/protect-files.sh
設定には以下のように書きます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
参考:
- https://code.claude.com/docs/ja/hooks-guide#保護されたファイルへの編集をブロックする
- https://code.claude.com/docs/ja/permissions#権限ルール構文
- https://code.claude.com/docs/ja/permissions#権限を管理する
- https://github.com/anthropics/claude-code/issues/30519
5. Compact後にコマンド実行結果をコンテキストに注入する
SessionStart.matcherでcompactを指定します。覚えておくと思い出したときに使えるかもしれません。
-
echo:CLAUDE.mdと異なり、matcher時にのみ注入するプロンプト向け -
git log --format="%s%n%b" -5: 最近のコミットのタイトルとメッセージを表示し、開発の姿勢と進捗を維持。全てのmatcherにつけておきました。-
%s: タイトル(subject) -
%b: ボディ(body、メッセージ本文) -
%n: 改行
-
matcherに使えるのは以下です。
- startup:
CLAUDE.mdとの使い分けが重要そうです。 - resume: セッションコンテキスト自体は生きているので、時間が経った後に戻ってくる想定で何か伝えることがあれば...。
- clear: コンテキストを開ける目的ならcompactを使ったほうが良いので、ここは空欄のほうが良いと考えられます。
- compact: おもにcompact後にechoで自動指示する用途が良いと思います。skillsの指示をするのも良いかもしれません。
参考: https://code.claude.com/docs/ja/hooks-guide#圧縮後にコンテキストを再注入する
6. 設定変更検知
user_settings、project_settings、local_settings、policy_settings、skillsで使えます。policy_settingsは組織設定のポリシーです。
設定変更を通知する設定をグローバルの方につけました。これに関しては付け得だと思います。
{
"hooks": {
"ConfigChange": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code changed its setting.\" with title \"Claude Code\"'"
}
]
}
]
}
}
参考: https://code.claude.com/docs/ja/hooks-guide#設定変更を監査する
7. 許可プロンプトを自動承認
permissions.allowは静的ですが、hooks経由では高度なスクリプトによって動的に設定することができます。正直私の手に余る設定だったので今回は置いておきます。
参考: https://code.claude.com/docs/ja/hooks-guide#特定の許可プロンプトを自動承認する
8. エージェントベースのhooks
プロンプトベース(つまり単一のLLM呼び出し)も可能です。
エージェントベースのhooksではサブエージェントを起動し目的を持って活動させます。サブエージェントなので完全にはコンテキストは受け継いでいないと思われます。
しかしこれも正直、活用するのは難しかったですが、覚えておくと便利そうなのは以下の仕様です。ドキュメントから引用しています。
https://code.claude.com/docs/ja/hooks#エージェント-フックの仕組み
エージェント hooks はプロンプト hooks と同じ
"ok"/"reason"応答形式を使用しますが、デフォルトのタイムアウトが 60 秒で、最大 50 ツール使用ターンです。
参考:
- https://code.claude.com/docs/ja/hooks-guide#エージェントベースの-hooks
- https://code.claude.com/docs/ja/hooks#エージェント-ベースのフック
9. セキュリティについて
Hooks作成をClaudeに頼む際には以下をコンテキストに入れましょう。
引用: https://code.claude.com/docs/ja/hooks#セキュリティ-ベストプラクティス
- **入力を検証およびサニタイズ**: 入力データを盲目的に信頼しないでください
- **常にシェル変数を引用**: `$VAR` ではなく `"$VAR"` を使用
- **パス トラバーサルをブロック**: ファイル パスで `..` をチェック
- **絶対パスを使用**: スクリプトの完全なパスを指定し、プロジェクト ルートに `"$CLAUDE_PROJECT_DIR"` を使用
- **機密ファイルをスキップ**: `.env`、`.git/`、キーなどを避ける
特に4つ目、絶対パスの指定に独自の変数が使えるのは整理もしやすくなって助かりますね。
6の設定変更検知と組み合わせるのも良いと思います。以下のように付け加えました。
{
"hooks": {
"ConfigChange": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code changed its setting.\" with title \"Claude Code\"'"
},
{
"type": "command",
"command": "echo 'もしhooksの変更なら以下に注意してください。- 入力を検証およびサニタイズ: 入力データを盲目的に信頼しないでください - 常にシェル変数を引用: $VAR ではなく \"$VAR\" を使用 - パストラバーサルをブロック: ファイルパスで .. をチェック - 絶対パスを使用: スクリプトの完全なパスを指定し、プロジェクトルートに \"$CLAUDE_PROJECT_DIR\" を使用 - 機密ファイルをスキップ: .env、.git/、キーなどを避ける'"
}
]
}
]
}
}
10. gitignore通知
これはドキュメント外です。まず私がやりたかったのはうっかり.gitignoreに環境変数のファイルを入れ損ねていないかを確認するスクリプトをSessionStartに書いています。(今思えばpre-commitのほうがよかったかもしれません...)

~/.claude/hooks/gitignore-notify.sh
#!/bin/bash
git rev-parse --git-dir > /dev/null 2>&1 || exit 0
SENSITIVE_FILES=(
".env"
".env.local"
".env.production"
".env.secret"
)
MISSING=()
for f in "${SENSITIVE_FILES[@]}"; do
git check-ignore -q "$f" 2>/dev/null || MISSING+=("$f")
done
if [ ${#MISSING[@]} -gt 0 ]; then
JOINED=$(IFS=', '; echo "${MISSING[*]}")
echo "{\"systemMessage\": \"警告: .gitignore に次のエントリが見つかりません: $JOINED\"}"
else
echo "{\"systemMessage\": \".gitignore チェック: ヒットしませんでした\"}"
fi
exit 0
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$HOME\"/.claude/hooks/gitignore-notify.sh"
}
]
}
]
ありがとうございました。誤りやより良い案などがあれば気軽にコメントしてほしいです。