はじめに
Claude Code はターミナル上で動作するAIコーディングエージェントです。コードの生成・修正・デバッグなどをAIに任せられる強力なツールですが、チームで運用するとなると「やってほしくないこと」の制御が課題になります。
本番DBへの接続、禁止されたパッケージマネージャの使用、機密ファイルの編集——こうした操作を人の目視確認だけで防ぐのは現実的ではありません。
そこで登場するのが Hooks です。本記事では、Hooks の基本概念から実践的なパターンまでを順を追って解説します。
Hooks とは
そもそも「Hook」って何?
ソフトウェアの世界で「Hook(フック)」とは、ある処理の前後に独自の処理を差し込む仕組みのことです。身近な例としては以下のようなものがあります。
-
Git Hooks:
git commitの前にコードのフォーマットチェックを走らせる - Webhook: サービスのイベント発生時に外部URLへ通知を送る
- React Hooks: コンポーネントのライフサイクルに処理を追加する
共通しているのは「既存の処理フローに、自分のコードを挟み込める」という点です。
Claude Code における Hooks
Claude Code の Hooks は、Claude Code の動作イベントにシェルスクリプトを差し込む仕組みです。2025年半ばに導入された比較的新しい機能で、以下のような特徴があります。
- stdin から JSON 形式でイベント情報を受け取ります
- exit code で Claude の動作を制御できます(許可・ブロック)
- シェルスクリプトなので、使い慣れたツールで書けます
CLAUDE.md がいわば「お願い(指示)」であるのに対し、Hooks は「強制力のあるルール」として機能します。CLAUDE.md に「npm は使わないで」と書いても Claude が守らない場合がありますが、Hooks でブロックすれば確実に防げます。
Hooks の動作モデル
Hooks は Claude Code のイベントループに介入します。以下に主要なイベントとデータの流れを示します。
主要イベント一覧
| イベント | 発火タイミング | 制御可否 |
|---|---|---|
SessionStart |
セッション開始時 | — |
UserPromptSubmit |
プロンプト送信時 | ブロック可 |
PreToolUse |
ツール実行前 | ブロック可 |
PostToolUse |
ツール実行成功後 | — |
PostToolUseFailure |
ツール実行失敗後 | — |
Notification |
通知送信時 | — |
Stop |
応答完了時 | — |
ブロック可能なイベントでは、Hook スクリプトの exit code が 2 のときに実行がブロックされ、stderr の内容がユーザーに表示されます。
exit code の意味
| exit code | 意味 |
|---|---|
0 |
許可(正常終了) |
1 |
Hook 自体のエラー(Claude は処理を継続します) |
2 |
操作をブロック(stderr の内容がユーザーに表示されます) |
exit code 1 はスクリプトのバグで発生することがあります。Claude の動作を止めるべきかどうかを明確に区別するため、意図的なブロックには必ず 2 を使いましょう。
パターン1: 危険操作のブロック(PreToolUse)
もっとも実用頻度が高いパターンです。PreToolUse イベントで特定のコマンドやファイル操作をブロックします。
npm/npx の使用をブロックして Bun に統一
「チームでは Bun を使う」というルールを Hooks で強制する例です。
#!/bin/bash
# hooks/block-npm.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [ "$TOOL" = "Bash" ]; then
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -qE '^\s*(npm|npx)\s'; then
echo "npm/npx は使用禁止です。bun を使ってください。" >&2
exit 2
fi
fi
exit 0
スクリプトの流れはシンプルです。
- stdin から JSON を読み取る(
INPUT=$(cat)) - 使おうとしているツール名を取得する(
jq -r '.tool_name') - Bash ツールの場合、コマンド内容をチェックする
- npm/npx が含まれていれば、exit code
2でブロックする
本番DB接続のブロック
#!/bin/bash
# hooks/block-prod-db.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [ "$TOOL" = "Bash" ]; then
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -qE 'DATABASE_URL.*prod|psql.*prod-db'; then
echo "本番データベースへの直接接続は禁止されています。" >&2
exit 2
fi
fi
exit 0
設定への登録方法
作成した Hook スクリプトは .claude/settings.json に登録します。/hooks コマンドで設定画面を開くこともできます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/block-npm.sh"
},
{
"type": "command",
"command": "bash .claude/hooks/block-prod-db.sh"
}
]
}
]
}
}
matcher フィールドでツール名を指定すると、該当ツールの使用時にだけ Hook が発火します。全ツールに適用したい場合は matcher を省略してください。
パターン2: ファイル編集の制御(PreToolUse)
特定ファイルの直接編集をブロックし、正規の手順を強制するパターンです。
#!/bin/bash
# hooks/protect-lockfile.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [ "$TOOL" = "Edit" ] || [ "$TOOL" = "Write" ]; then
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.file')
if echo "$FILE" | grep -qE '(package-lock\.json|bun\.lockb|yarn\.lock)'; then
echo "ロックファイルの直接編集は禁止です。パッケージマネージャ経由で更新してください。" >&2
exit 2
fi
fi
exit 0
パターン3: 自動フォーマット(PostToolUse)
ファイル編集後に自動でフォーマッタを走らせるパターンです。PostToolUse は同期実行されるため、フォーマット結果が次の Claude の思考に反映されます。
#!/bin/bash
# hooks/auto-format.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [ "$TOOL" = "Edit" ] || [ "$TOOL" = "Write" ]; then
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.file')
EXT="${FILE##*.}"
case "$EXT" in
ts|tsx|js|jsx)
npx prettier --write "$FILE" 2>/dev/null
;;
py)
ruff format "$FILE" 2>/dev/null
;;
go)
gofmt -w "$FILE" 2>/dev/null
;;
esac
fi
exit 0
パターン4: ログ収集(非同期)
イベントを記録してセッションの振り返りに使うパターンです。Claude の処理速度に影響を与えないよう、async: true で実行します。
#!/bin/bash
# hooks/log-event.sh
INPUT=$(cat)
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
LOG_DIR="${HOME}/.claude/logs"
mkdir -p "$LOG_DIR"
echo "${TIMESTAMP} ${INPUT}" >> "${LOG_DIR}/hooks-$(date +%Y%m%d).jsonl"
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/log-event.sh",
"async": true
}
]
}
],
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/log-event.sh",
"async": true
}
]
}
]
}
}
sync と async の使い分け
判断基準はシンプルです。Hook の結果で Claude の動作を制御する必要があるかどうかで決めます。
| ユースケース | async |
|---|---|
| 操作のブロック・許可判定 | false |
| フォーマッタの自動実行 | false |
| ログ記録・メトリクス収集 | true |
| 外部APIへの通知送信 | true |
| セッション統計の集計 | true |
async: false(デフォルト)の Hook は Claude の応答をブロックします。ネットワーク通信を含む処理を同期で実行すると、ユーザー体験が著しく悪化するため注意してください。
設計のベストプラクティス
1. Hook スクリプトはリポジトリに含める
.claude/hooks/ ディレクトリにスクリプトを配置し、.claude/settings.json から参照します。これによりチーム全員が同じガードレールで作業できます。
.claude/
├── hooks/
│ ├── block-npm.sh
│ ├── block-prod-db.sh
│ ├── protect-lockfile.sh
│ └── auto-format.sh
├── settings.json # チーム共有(git管理)
└── settings.local.json # 個人設定(gitignore)
2. stderr のメッセージを actionable にする
# Bad: 何が起きたかだけ
echo "この操作は許可されていません。" >&2
# Good: 代替手段を示す
echo "npm は使用禁止です。代わりに 'bun install' を使ってください。" >&2
ブロック時の stderr は Claude にもフィードバックされます。代替手段を明示すると、Claude が自動的に正しいコマンドに切り替えてくれるため、ユーザーの手間を減らせます。
3. CLAUDE.md との役割分担を明確にする
| 制御手段 | 強制力 | 適した用途 |
|---|---|---|
| CLAUDE.md | 弱(指示) | コーディング規約、命名規則、設計方針 |
| Hooks | 強(プログラム) | 本番環境保護、ツール制限、自動フォーマット |
| settings.json の permissions | 強(設定) | ファイル読み書き範囲、コマンド実行許可 |
CLAUDE.md に書くだけで十分なことを Hooks で制御すると、メンテナンスコストが不必要に膨らみます。「破ったら事故になる」ルールだけを Hooks で強制するのが実践的です。
まとめ
Hooks は Claude Code の動作に強制力のあるルールを追加する仕組みです。Git Hooks のように「処理の前後にスクリプトを差し込む」というシンプルな仕組みですが、チーム開発でのガードレール構築には非常に効果的です。
- PreToolUse で危険操作をブロックし、ガードレールを設けましょう
- PostToolUse で自動フォーマットや品質チェックを差し込めます
- async 設定で Claude の応答速度への影響を制御できます
- スクリプトはリポジトリに含め、チーム全員で共有しましょう
重要なのは、Hooks を過剰に使わないことです。すべてのイベントに Hook を仕込むと、設定の見通しが悪くなりデバッグも困難になります。「CLAUDE.md で十分か、Hooks で強制すべきか」を見極め、本当にブロックが必要なケースに絞って導入するのが、長く運用できる設計につながります。