Claude Code hooksとは — イベント駆動の自動化フレームワーク
hooksの役割 — 「確率的」ではなく「確定的」な自動化
Claude Code hooksとは、Claude Codeのライフサイクル上の特定のタイミングで自動的にシェルコマンドやスクリプトを実行する仕組みです。たとえば「ファイルを編集した直後にPrettierを実行する」「危険なコマンドの実行前にブロックする」「Claudeが応答を完了したらデスクトップ通知を送る」といった処理を、設定ファイルに書くだけで実現できます。
ここで重要なのは、hooksはAIの判断に依存しない確定的な自動化だという点です。CLAUDE.mdに「ファイル編集後はPrettierを実行してください」と書いても、Claudeが毎回必ず実行してくれる保証はありません。LLMの出力は本質的に確率的だからです。一方、hooksはイベントが発生すれば100%確実に実行されるため、コードフォーマットやテスト実行などの「絶対にやるべき処理」に最適です。
hooksとCLAUDE.mdの違い — 使い分けの基準
Claude Codeの設定にはCLAUDE.mdもありますが、hooksとは役割が明確に異なります。
| 項目 | CLAUDE.md | hooks |
|---|
| 実行の確実性 | 確率的(AIが判断) | 確定的(100%実行) |
| 設定方法 | Markdownファイル | settings.jsonのJSON設定 |
| 適用タイミング | セッション全体を通じて常に | 特定イベント発生時のみ |
| 用途 | コーディング規約、プロジェクトルール | フォーマット実行、テスト実行、ブロック処理 |
| AIの行動への影響 | 「こう書いてください」と依頼 | 「このタイミングでこのコマンドを実行」と強制 |
わかりやすく言えば、CLAUDE.mdは「AIへのお願い」であり、hooksは「システムへの命令」です。両者を組み合わせることで、AI駆動開発の品質と効率を最大化できます。
hooksが解決する3つの課題
ShiftBの受講生142名の開発データを分析すると、hooks導入前に最も多かった課題は以下の3つでした。
- フォーマットの不統一(受講生の74%が経験):Claudeがファイル編集後にPrettierを実行し忘れ、コミット時にlintエラーが発生
- 保護ファイルの誤編集(受講生の41%が経験):.envファイルやpackage-lock.jsonをAIが勝手に書き換えてしまう
- 通知の見逃し(受講生の68%が経験):Claudeが応答を完了しているのに気づかず、ターミナルを監視し続ける
hooksを導入すれば、これらはすべて設定ファイル数行で自動化できます。
4つのハンドラタイプ — command、http、prompt、agent
Claude Code hooksには4つのハンドラタイプがあります。
| タイプ | 説明 | 主な用途 |
|---|
command | シェルコマンドを実行。stdinでJSON受信、exit codeで結果を返す | フォーマット実行、ファイルブロック、通知送信 |
http | HTTPエンドポイントにPOST送信 | 外部サービスへのログ送信、Webhook連携 |
prompt | Claudeモデルに1回のプロンプトで判断させる | コード品質チェック、タスク完了判定 |
agent | サブエージェントを起動し、ファイル読み取りやコマンド実行で検証 | テスト結果の検証、コードベース全体のチェック |
最もよく使うのはcommandタイプです。まずはこれを使いこなし、必要に応じて他のタイプを組み合わせていくのがおすすめです。

hooksの仕組み — settings.jsonでの設定方法と実行タイミング
settings.jsonの基本構造
hooksの設定は、settings.jsonファイルのhooksプロパティに記述します。基本構造は以下のとおりです。
{
"hooks": {
"イベント名": [
{
"matcher": "フィルタパターン(正規表現)",
"hooks": [
{
"type": "command",
"command": "実行するシェルコマンド",
"timeout": 600,
"statusMessage": "実行中に表示するメッセージ"
}
]
}
]
}
}
構造は3段階のネストになっています:
- イベント名(例:
PostToolUse):どのタイミングで発火するか - matcherグループ:そのイベントのうち、さらに条件を絞り込むフィルタ
- hookハンドラ:実際に実行するコマンドやスクリプト
設定ファイルの配置場所とスコープ
hooksの設定は複数の場所に配置でき、それぞれスコープが異なります。
| 配置場所 | スコープ | Git共有 | 用途 |
|---|
~/.claude/settings.json | 全プロジェクト共通 | 不可 | 通知設定、個人的な自動化 |
.claude/settings.json | プロジェクト単位 | 可能 | チーム共通のフォーマッタ、保護ルール |
.claude/settings.local.json | プロジェクト単位 | 不可(gitignore) | 個人的なプロジェクト設定 |
| マネージドポリシー | 組織全体 | 管理者制御 | 企業のセキュリティポリシー |
最も重要なポイント:チームで共有すべきhooks(フォーマッタ、保護ファイルルール等)は.claude/settings.jsonに配置し、Gitリポジトリにコミットしましょう。個人的な通知設定などは~/.claude/settings.jsonに配置します。
exit codeの仕組み — 0、2、その他の意味
commandタイプのhooksは、exit code(終了コード)で結果をClaude Codeに伝えます。この仕組みを理解することが、hooks活用の鍵です。
| exit code | 意味 | 動作 |
|---|
| 0 | 成功 | アクションを続行。stdoutのJSONを解析。UserPromptSubmitやSessionStartでは、stdoutがClaudeのコンテキストに追加される |
| 2 | ブロック | アクションを阻止。stderrのテキストがClaudeにフィードバックされ、AIが行動を修正する |
| その他 | 非ブロックエラー | アクションは続行。stderrはverboseモード(Ctrl+O)でのみ表示 |
たとえば、PreToolUseフックでexit 2を返すと、そのツール実行がキャンセルされ、stderrに書いた理由がClaudeに伝わります。Claudeはその理由を読んで、別のアプローチを試みます。
入出力のデータフロー
hooksはClaude Codeとstdin、stdout、stderr、exit codeの4つのチャネルで通信します。イベントが発火すると、Claude CodeはイベントデータをJSON形式でhookスクリプトのstdinに渡します。
たとえば、ClaudeがBashコマンドを実行しようとした際のPreToolUseフックには、以下のようなJSONが渡されます:
{
"session_id": "abc123",
"cwd": "/Users/yourname/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
このJSONをjqなどで解析し、条件に応じてexit codeを返すのがhookスクリプトの基本パターンです。
構造化JSON出力 — より高度な制御
exit codeだけでなく、stdoutにJSONを出力することでより細かい制御が可能です。たとえば、PreToolUseフックでツール実行を拒否しつつ理由を伝える場合:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "grepの代わりにrgを使ってください(パフォーマンス向上のため)"
}
}
permissionDecisionには"allow"(許可)、"deny"(拒否)、"ask"(ユーザーに確認)の3つの値を指定できます。
AI時代のアプリ開発コース
ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。
AIシフトコースを見る →対応イベント一覧と使いどころ
主要イベント一覧 — 全26イベント
Claude Code hooksは26種類のイベントに対応しています。以下が全イベントの一覧です。
| イベント名 | 発火タイミング | ブロック可能 |
|---|
SessionStart | セッション開始・再開時 | - |
UserPromptSubmit | プロンプト送信時(Claude処理前) | ○ |
PreToolUse | ツール実行前 | ○ |
PermissionRequest | 権限ダイアログ表示時 | ○ |
PermissionDenied | ツール実行が拒否された時 | - |
PostToolUse | ツール実行成功後 | - |
PostToolUseFailure | ツール実行失敗後 | - |
Notification | Claude Codeが通知を送信した時 | - |
SubagentStart | サブエージェント起動時 | - |
SubagentStop | サブエージェント終了時 | ○ |
TaskCreated | TaskCreate経由でタスク作成時 | ○ |
TaskCompleted | タスク完了マーク時 | ○ |
Stop | Claudeが応答を完了した時 | ○ |
StopFailure | APIエラーでターン終了時 | - |
TeammateIdle | エージェントチームのメンバーがアイドル状態になる時 | ○ |
InstructionsLoaded | CLAUDE.mdやrulesファイルが読み込まれた時 | - |
ConfigChange | 設定ファイルが変更された時 | ○ |
CwdChanged | 作業ディレクトリが変更された時 | - |
FileChanged | 監視対象ファイルが変更された時 | - |
WorktreeCreate | worktreeが作成される時 | ○ |
WorktreeRemove | worktreeが削除される時 | - |
PreCompact | コンテキスト圧縮の前 | - |
PostCompact | コンテキスト圧縮の後 | - |
Elicitation | MCPサーバーがユーザー入力を要求した時 | ○ |
ElicitationResult | MCP elicitationへのユーザー応答後 | ○ |
SessionEnd | セッション終了時 | - |
最もよく使う5つのイベント
26イベントすべてを使う必要はありません。実務でよく使うのは以下の5つです。ShiftBの受講生の利用データでも、これら5つで全hooks設定の約90%を占めています。
- PostToolUse:ファイル編集後のフォーマット自動実行。最も利用頻度が高い
- PreToolUse:危険な操作のブロック。保護ファイルの書き換え防止など
- Notification:デスクトップ通知。Claudeの応答完了を見逃さない
- Stop:Claudeの応答完了時の検証。タスク完了チェックなど
- SessionStart:セッション開始時のコンテキスト注入。コンパクト後の情報復元など
matcherパターン — イベントをさらに絞り込む
matcherは正規表現パターンで、イベントの発火条件をさらに絞り込みます。イベントごとにmatcherが何にマッチするかが異なるので注意してください。
| イベント | matcherの対象 | 例 |
|---|
| PreToolUse / PostToolUse | ツール名 | Edit|Write、Bash、mcp__.* |
| SessionStart | セッション開始方法 | startup、resume、compact |
| Notification | 通知タイプ | permission_prompt、idle_prompt |
| ConfigChange | 設定ソース | user_settings、project_settings |
| FileChanged | ファイル名(basename) | .envrc、.env |
| SessionEnd | 終了理由 | clear、resume、logout |
matcherを空文字列""にすると、そのイベントのすべての発火で実行されます。パフォーマンスのためにも、可能な限りmatcherで絞り込むことをおすすめします。
ifフィールド — ツール名と引数で絞り込む
matcherはツール名でしか絞り込めませんが、ifフィールドを使うとツール名と引数の両方で絞り込めます。 たとえば、Bashツールのうちgitコマンドだけにフックを適用する場合:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-git-policy.sh"
}
]
}
]
}
}
ifフィールドはパーミッションルールと同じ構文を使います。"Bash(git *)"はgitで始まるBashコマンドのみにマッチし、"Edit(*.ts)"はTypeScriptファイルの編集のみにマッチします。
実践!開発ワークフロー自動化レシピ集
レシピ1:ファイル編集後にPrettierを自動実行
最も人気のあるhooks設定です。Claudeがファイルを編集するたびにPrettierが自動実行され、コードフォーマットが常に統一されます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
この設定を.claude/settings.jsonに配置してGitにコミットすれば、チーム全員のClaude Codeで同じフォーマッタが自動実行されます。jqコマンドでstdinのJSONから編集されたファイルパスを抽出し、Prettierに渡しています。
注意:jqがインストールされていない場合は、brew install jq(macOS)またはapt-get install jq(Linux)でインストールしてください。
レシピ2:保護ファイルの書き換えをブロック
.envやpackage-lock.json、.git/ディレクトリ内のファイルなど、AIが触るべきでないファイルへの編集をブロックします。
まず、hookスクリプトを作成します。
#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // 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
done
exit 0
次に、settings.jsonでこのスクリプトを登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
exit 2でブロックし、stderrに理由を出力すると、Claudeがその理由を読んで別のアプローチを試みます。たとえば「.envの直接編集はブロックされました」と伝われば、Claudeは.env.exampleの更新を提案するようになります。
レシピ3:デスクトップ通知で応答完了を知らせる
Claudeが応答を完了したり、権限の確認で入力待ちになったりした際に、デスクトップ通知を送ります。ターミナルを監視し続ける必要がなくなるため、マルチタスクが可能になります。
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Codeが入力を待っています\" with title \"Claude Code\"'"
}
]
}
]
}
}
上記はmacOSの例です。Linuxの場合はnotify-sendコマンドに置き換えてください。この設定は個人用なので、~/.claude/settings.jsonに配置するのが適切です。
レシピ4:コンパクト後にコンテキストを復元する
Claude Codeのコンテキストウィンドウがいっぱいになると、会話が自動的に要約(コンパクト)されます。このとき、重要な情報が失われることがあります。SessionStartフックのcompact matcherを使えば、コンパクト後に重要な情報を再注入できます。
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'リマインダー: パッケージマネージャはBunを使う。コミット前にbun testを実行。現在のスプリント: 認証リファクタリング。'"
}
]
}
]
}
}
echoの代わりにgit log --oneline -5などのコマンドを使えば、直近のコミット履歴を動的に注入することも可能です。
レシピ5:Stopフックでタスク完了を検証する
Claudeが「完了しました」と言っても、実際にはタスクが未完了な場合があります。promptタイプのStopフックを使えば、Claudeの応答を別のモデルが検証し、不十分であれば作業を継続させられます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "すべてのタスクが完了しているか確認してください。未完了のタスクがある場合は {\"ok\": false, \"reason\": \"残りのタスク内容\"} で応答してください。"
}
]
}
]
}
}
promptタイプのフックは、Claudeモデル(デフォルトはHaiku)にyes/noの判断をさせる仕組みです。"ok": falseが返された場合、Claudeは作業を継続し、reasonに書かれた内容を次の指示として使います。
レシピ6:Bashコマンドをログに記録する
Claudeが実行したすべてのBashコマンドをログファイルに記録します。セキュリティ監査やデバッグに便利です。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
}
]
}
]
}
}
レシピ7:権限プロンプトの自動承認
特定の権限プロンプト(たとえばExitPlanMode)を自動承認し、毎回の確認を省略できます。
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}
注意:matcherは必ず狭い範囲に限定してください。matcherを空にすると、すべての権限プロンプト(ファイル書き込みやシェルコマンドを含む)が自動承認されてしまいます。セキュリティリスクが極めて高いため、必ず特定のツール名を指定しましょう。

hooks設計のベストプラクティスとアンチパターン
ベストプラクティス1:hookは小さく・単一責務に
1つのhookに複数の処理を詰め込まないでください。1つのhookには1つの責務が原則です。
悪い例:1つのhookに複数の処理
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path'); npx prettier --write $FILE && npx eslint --fix $FILE && npm test -- --related $FILE && echo 'All checks passed'"
}
]
}
]
}
}
良い例:個別のhookに分離
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
"statusMessage": "Formatting..."
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx eslint --fix",
"statusMessage": "Linting..."
}
]
}
]
}
}
分離するメリットは3つあります:デバッグが容易(どのhookが失敗したか特定しやすい)、並列実行(matcherグループ内のhooksは並列実行される)、個別の無効化が可能です。
ベストプラクティス2:タイムアウトを適切に設定する
hookのデフォルトタイムアウトはcommandで600秒(10分)、promptで30秒、agentで60秒です。ほとんどのhookは数秒で完了するはずなので、必要に応じてtimeoutを短く設定しましょう。
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
"timeout": 30,
"statusMessage": "Formatting with Prettier..."
}
ベストプラクティス3:statusMessageで進捗を伝える
hookが実行されている間、ユーザーには何が起きているかわかりません。statusMessageを設定すると、hookの実行中にスピナーメッセージが表示されます。
アンチパターン1:Stopフックの無限ループ
Stopフックの最も危険なバグは無限ループです。Claudeが応答を完了するたびにStopフックが発火し、フックが「まだ完了していない」と判定すると、Claudeが再度作業を行い、また応答を完了し、再びStopフックが発火し......という無限ループに陥ります。
対策:フック入力のJSONに含まれるstop_hook_activeフィールドを確認し、trueの場合は早期リターンしてください。
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Claudeの停止を許可
fi
# ... 通常のhookロジック
アンチパターン2:シェルプロファイルのecho文による出力汚染
Claude CodeはhookスクリプトをBashシェルで実行しますが、その際に~/.zshrcや~/.bashrcが読み込まれます。これらのファイルに無条件のecho文が含まれていると、hookのJSON出力の前にその出力が挿入され、JSONのパースに失敗します。
対策:シェルプロファイルのecho文をインタラクティブシェルの場合のみ実行するよう修正してください。
# ~/.zshrc や ~/.bashrc で
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
アンチパターン3:matcherなしの広範なフック
PreToolUseやPermissionRequestでmatcherを空にするのは危険です。すべてのツール実行に対してフックが発火するため、パフォーマンスが低下し、意図しないブロックが発生する可能性があります。
対策:必ず特定のツール名をmatcherに指定してください。複数のツールを対象にする場合は、パイプ|で区切ります(例:Edit|Write|MultiEdit)。
AI時代のアプリ開発コース
ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。
AIシフトコースを見る →hooksとCLAUDE.md・カスタムコマンドの組み合わせ技
CLAUDE.md + hooks = 最強の開発環境
CLAUDE.mdとhooksは補完関係にあります。CLAUDE.mdは 「AIがコードを書くときのルール」を定義し、hooksは「AIがアクションを実行したときの自動処理」を定義します。
具体的な役割分担の例:
- CLAUDE.md:「Tailwindはgap-10の書き方をし、gap-[40px]は使わない」
- hooks:「ファイル編集後にPrettierとESLintを自動実行する」
- CLAUDE.md:「APIルートではzodで入力バリデーションを行う」
- hooks:「.envファイルの直接編集をブロックする」
CLAUDE.mdで「書き方のルール」を伝え、hooksで「絶対に守らせたいこと」を強制する。この組み合わせにより、AIの出力品質と開発プロセスの両方を同時に向上できます。
カスタムスラッシュコマンドとの連携
Claude Codeのカスタムスラッシュコマンド(.claude/commands/)とhooksを組み合わせると、さらに強力なワークフローを構築できます。
たとえば、/deployというカスタムコマンドを作成し、デプロイ前のチェックをhooksで自動化するパターン:
# .claude/commands/deploy.md
デプロイ前チェックリストを実行し、問題がなければVercelにデプロイしてください:
1. npm run build でビルドが成功するか確認
2. npm test でテストがすべて通るか確認
3. git status で未コミットの変更がないか確認
4. すべてOKなら vercel --prod を実行
この/deployコマンドと組み合わせて、Stopフックで 「デプロイが完了したらSlackに通知する」といった処理を追加できます。
実践的な組み合わせ例:フルスタック開発ワークフロー
以下は、ShiftBの受講生がフルスタック開発で使っている典型的な組み合わせです。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
"statusMessage": "Formatting..."
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
],
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'パッケージマネージャ: bun / テスト: bun test / lint: bun lint'"
}
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Codeが待機中です\" with title \"Claude Code\"'"
}
]
}
]
}
}
MCPサーバーのツールにもhooksを適用する
MCPツールはmcp__サーバー名__ツール名という命名規則を使います。matcherで正規表現を使えば、特定のMCPサーバーの全ツールにフックを適用できます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__github__.*",
"hooks": [
{
"type": "command",
"command": "echo \"GitHub tool called: $(jq -r '.tool_name')\" >&2"
}
]
}
]
}
}
これにより、GitHub MCPサーバー経由のすべてのツール呼び出しをログに記録したり、特定の操作をブロックしたりできます。
ShiftBで実際に使っているhooks設定を公開
ShiftBのプロジェクト共通hooks設定
ShiftBではすべてのプロジェクトで以下のhooks設定を共通で使っています。この設定は.claude/settings.jsonに配置し、Gitにコミットしています。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null || true",
"timeout": 30,
"statusMessage": "Auto-formatting..."
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
ポイント:Prettierコマンドの末尾に2>/dev/null || trueを付けています。これにより、Prettierが対応していないファイル形式(画像など)を編集した場合でもエラーにならず、hookが正常に完了します。
ShiftBの保護ファイルスクリプト
ShiftBのプロジェクトで実際に使っている保護ファイルスクリプトです。受講生にもこのスクリプトを共有しており、導入後の保護ファイル誤編集事故はゼロ件を達成しています。
#!/bin/bash
# .claude/hooks/protect-files.sh
# ShiftBプロジェクト共通の保護ファイルスクリプト
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# 空パスの場合はスキップ
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# 保護対象パターン
PROTECTED_PATTERNS=(
".env"
".env.local"
"package-lock.json"
"bun.lockb"
"pnpm-lock.yaml"
"yarn.lock"
".git/"
"node_modules/"
".next/"
"supabase/migrations/"
)
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "BLOCKED: '$FILE_PATH' is a protected file (matched pattern: '$pattern')." >&2
echo "If you need to modify this file, please ask the user for explicit permission." >&2
exit 2
fi
done
exit 0
ShiftBのグローバルhooks設定
僕個人の~/.claude/settings.jsonには、以下のグローバルhooksを設定しています。
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Codeが入力を待っています\" with title \"Claude Code\"'"
}
]
}
],
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo '=== コンパクト後のリマインダー === \n- パッケージマネージャ: bun\n- テスト: bun test\n- Tailwind: gap-10形式を使い、gap-[40px]は禁止\n- 日本語でコミュニケーション'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt",
"async": true
}
]
}
]
}
}
特にBashコマンドのログ記録は"async": trueにしています。非同期実行にすることで、ログの書き込みを待たずにClaude Codeの処理が続行されるため、パフォーマンスへの影響がゼロです。
hooks導入の効果 — ShiftB受講生のデータ
ShiftBの受講生142名のうち、hooksを導入した67名のデータを分析しました。
| 指標 | hooks導入前 | hooks導入後 | 改善率 |
|---|
| フォーマット修正の手作業 | 1セッションあたり平均6.2回 | 0回 | -100% |
| 保護ファイルの誤編集 | 月平均1.3件 | 0件 | -100% |
| Claudeの応答見逃し | 1日平均3.8回 | 0.2回 | -95% |
| lint/テストエラーの検出 | コミット後に気付く(42%) | 編集直後に検出(98%) | +56pt |
| 1セッションの生産性(体感) | - | - | 平均+35%向上 |
特にフォーマット修正と保護ファイルの誤編集がともに完全にゼロになった点が大きいです。これらは「hooksがなければ毎回手動でやるか、事故が起きてから対処するしかなかった」処理であり、hooksの確定的な自動化の威力を示しています。
よくある質問(FAQ)
Q1. hooksの設定を変更したら、Claude Codeの再起動は必要ですか?
通常は不要です。Claude Codeにはファイルウォッチャーが組み込まれており、settings.jsonの変更を自動的に検出します。ただし、まれに検出されない場合があるため、その場合はセッションを再起動してください。変更後は/hooksコマンドで正しく反映されているか確認するのがおすすめです。
Q2. hookスクリプトのデバッグ方法は?
3つの方法があります。まず、Ctrl+Oでverboseモードをオンにすると、hookの出力がトランスクリプトに表示されます。次に、claude --debugで起動すると、どのhookがマッチしたか、exit codeは何だったかの詳細が表示されます。最後に、手動でテストする方法があります。サンプルJSONをパイプでスクリプトに渡して動作を確認します。
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $? # exit codeを確認
Q3. hooksはCLAUDE.mdの代わりになりますか?両方必要ですか?
両方必要です。CLAUDE.mdとhooksは役割が明確に異なります。CLAUDE.mdは「AIがコードを書くときのルールや知識」を定義し、hooksは「特定のイベントで確実に実行する処理」を定義します。たとえば、「Tailwindでgap-10を使う」はCLAUDE.mdに書き、「ファイル編集後にPrettierを実行する」はhooksに設定します。CLAUDE.mdなしでhooksだけ使っても、AIの出力品質は向上しません。
Q4. hooksのcommandが「command not found」になります。どうすればいいですか?
hookスクリプトは非インタラクティブシェルで実行されるため、PATHの設定が通常のターミナルと異なる場合があります。絶対パスを使うのが確実です。たとえばnpx prettierの代わりに/usr/local/bin/npx prettierを使うか、$CLAUDE_PROJECT_DIR環境変数を使ってプロジェクトルートからの相対パスでスクリプトを参照してください。また、スクリプトファイルに実行権限が付与されているかも確認してください(chmod +x ./my-hook.sh)。
Q5. hookを一時的に無効にする方法はありますか?
settings.jsonに"disableAllHooks": trueを追加すると、すべてのhooksを一括で無効化できます。特定のhookだけを無効にしたい場合は、そのhookの設定を削除するか、matcherに絶対にマッチしないパターン(例:"matcher": "DISABLED")を設定する方法があります。
Q6. 複数のPreToolUseフックが同じツールに対して設定されている場合、どの結果が優先されますか?
最も制限的な結果が優先されます。複数のフックが同じツールにマッチした場合、すべてのフックが並列実行され、1つでもdenyを返せばツール実行はキャンセルされます。1つがaskを返せば、他がallowでもユーザーに確認が表示されます。すべてのフックのadditionalContextはマージされてClaudeに渡されます。
Q7. hooks導入で最初に設定すべきものは何ですか?
ShiftBの受講生データでは、以下の3つを最初に設定した受講生の満足度が最も高いという結果でした:①Notification(デスクトップ通知)、②PostToolUseのPrettier自動実行、③PreToolUseの保護ファイルブロック。この3つだけで開発体験が大幅に改善されるため、まずはこの3つから始めることをおすすめします。設定時間も合計10分程度です。
まとめ
Claude Code hooksは、AI駆動開発を「確率的なアシスタント利用」から「確定的な自動化ワークフロー」に進化させるフレームワークです。この記事のポイントをまとめます。
- hooksはイベント駆動の確定的な自動化:CLAUDE.mdの「お願い」とは異なり、100%確実に実行される
- settings.jsonで簡単に設定:イベント名・matcher・コマンドの3段階構造
- まず3つから始める:通知、Prettierの自動実行、保護ファイルのブロック
- hooksは小さく単一責務に:1つのhookに複数の処理を詰め込まない
- CLAUDE.mdと組み合わせて最強の環境を構築:「ルール」と「自動化」の両輪で品質を担保
- .claude/settings.jsonはGitにコミット:チーム全体で自動化ワークフローを共有
ShiftBの受講生データでは、hooksを導入しただけでフォーマット修正の手作業が完全にゼロになり、保護ファイルの誤編集事故もゼロになりました。設定にかかる時間はわずか10分です。
まだhooksを設定していない方は、まずNotificationフック(デスクトップ通知)から始めてみてください。ターミナルを監視する時間がなくなるだけで、AI駆動開発の体験が劇的に変わります。