Hooks で振る舞いを制御する
重要キーワード
Hooks (フック)
特定のイベント発生時に シェルコマンドを実行 する仕組み。 メモリやプロンプトでは強制できない、決定論的な自動化 に向きます。
イベント例
SessionStart: セッション開始時PreToolUse: ツール実行前PostToolUse: ツール実行後UserPromptSubmit: ユーザーがプロンプト送信した時Stop: Claude の応答終了時Notification: 通知イベントPreCompact: コンテキスト圧縮前
設定例 (.claude/settings.json)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "ruff check $CLAUDE_PROJECT_DIR" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "echo Done" }
]
}
]
}
}
Hook の役割
- 強制ルール: 編集後に必ず lint を走らせる、フォーマッタを通す。
- 通知: タスク完了時に音/通知を出す。
- ログ: 全プロンプトをファイルに記録 (監査)。
- ガード: 危険なコマンドをブロック (PreToolUse で exit code 2)。
- 自動 git checkpoint: 編集前に commit を打つ。
exit code の意味
0: 正常 (続行)。2: ブロック (Claude がそのアクションを取り消す)。stderr の文言が次のターンに渡されるので、Claude が修正を試みる。- それ以外: 警告として扱われる。
環境変数
Hook 実行時には便利な環境変数が渡されます:
- $CLAUDE_PROJECT_DIR: プロジェクトルート
- $CLAUDE_TOOL_NAME: 呼ばれたツール名 (PreToolUse / PostToolUse)
- $CLAUDE_TOOL_INPUT_JSON: ツール入力 (PreToolUse)
実用例 1: 編集後フォーマット
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "prettier --write $CLAUDE_PROJECT_DIR && eslint --fix $CLAUDE_PROJECT_DIR"
}]
}]
}
}
実用例 2: 危険コマンドブロック
PreToolUse で Bash ツールに対し、入力に rm -rf / などが含まれていたら exit 2 でブロック。
#!/bin/bash
# .claude/scripts/guard-bash.sh
input=$(echo "$CLAUDE_TOOL_INPUT_JSON" | jq -r '.command')
if [[ "$input" =~ rm\ -rf\ / ]]; then
echo "Blocked: dangerous rm -rf detected" >&2
exit 2
fi
実用例 3: ロギング
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{ "type": "command", "command": "echo "$(date -Iseconds) $(pwd) $CLAUDE_USER_PROMPT" >> ~/claude.log" }]
}]
}
}
注意
- Hook は ホスト OS 上で実行される ので、悪意あるリポジトリの settings.json に注意。
- 重い処理を入れると体感が悪化する。lint は変更ファイルだけに絞るなど工夫を。
settings.local.json(gitignore 推奨) に個人設定、settings.jsonをチーム共有用、と分けるのが定番。
本番運用パターン集 (コピペで使える)
パターン 1: 編集後に変更ファイルだけフォーマット (高速)
「全プロジェクトを毎回フォーマット」は遅すぎる。変更ファイルだけ に絞る:
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "echo \"$CLAUDE_TOOL_INPUT_JSON\" | jq -r '.file_path' | xargs -I{} ruff format {}"
}]
}]
}
}
→ 編集された そのファイルだけ ruff format が走る。100 ms 以内で終わる。
パターン 2: シークレット流出をブロック (PreToolUse)
.env や鍵ファイルへの 書き込み・読み込み を未然にブロック:
.claude/scripts/guard-secrets.sh:
#!/usr/bin/env bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // empty')
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
# .env, *.pem, *.key, secrets/ への書き込みを拒否
if [[ "$file" =~ \.env(\..+)?$ ]] || [[ "$file" =~ \.(pem|key|p12)$ ]] || [[ "$file" =~ ^secrets/ ]]; then
echo "Blocked: writes to secret files are forbidden ($file)" >&2
exit 2
fi
# Bash で curl https://... | bash 系を拒否
if [[ "$cmd" =~ curl.*\|.*bash ]] || [[ "$cmd" =~ wget.*\|.*sh ]]; then
echo "Blocked: piping curl/wget to shell is forbidden" >&2
exit 2
fi
exit 0
settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit|Write|Bash",
"hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/scripts/guard-secrets.sh" }]
}]
}
}
→ Claude が .env を編集しようとした瞬間に止まり、stderr の文言を見て自己修正を試みる。
パターン 3: 全プロンプトをログに残す (監査)
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "jq -r '\(.timestamp) \(.prompt)' >> $HOME/.claude/audit.log"
}]
}]
}
}
→ 全プロンプトが ~/.claude/audit.log に記録される。コンプライアンス要件のある現場で必須。
パターン 4: セッション終了時に WIP コミット
「Claude Code を閉じる前に必ず checkpoint」を強制:
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && git diff --quiet || (git add -A && git commit -m 'wip: claude session end')"
}]
}]
}
}
→ 未コミット変更があればセッション終了時に自動 commit。作業ロスト防止。
パターン 5: 危険 git コマンドの追加ガード
PreToolUse で Bash を Inspect:
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command' < /dev/stdin)
case "$cmd" in
*"git push --force"*|*"git push -f "*)
echo "Blocked: force push is forbidden. Use --force-with-lease if necessary, manually." >&2
exit 2
;;
*"git reset --hard"*)
echo "Blocked: hard reset can lose work. Confirm manually." >&2
exit 2
;;
*"rm -rf "*)
echo "Blocked: rm -rf is restricted. Use a more specific pattern." >&2
exit 2
;;
esac
exit 0
Hooks デバッグのコツ
/hooksで現在登録されている hook を確認- 失敗時は
~/.claude/logs/hooks.logに stderr が記録される (環境による) commandの中でset -xを入れると実行コマンドが見える- 開発中は
echoで$CLAUDE_TOOL_INPUT_JSONを出力し、構造を確認
演習: 編集後フォーマッタを Hook で強制
Python プロジェクトで Edit / Write 直後に ruff format が走る Hook を設定してください。
.claude/settings.jsonを編集 (もしくは新規作成)- PostToolUse + matcher "Edit|Write"
- command に
ruff format $CLAUDE_PROJECT_DIRを設定 - Claude Code セッションで何かファイルを編集し、自動フォーマットを確認
演習: 危険コマンドブロックの Hook
Bash ツール呼び出しで rm -rf が含まれていたらブロックする PreToolUse Hook を作ってください。
Hook スクリプト (bash) を .claude/scripts/guard.sh に置き、settings.json から呼び出します。
まとめ
お疲れ様でした!