hookを書きたい。でも何から始めればいいか分からない。
公式ドキュメントは読んだ。仕組みは理解した。でも「じゃあ実際にファイルを作って動かすまで」が遠い。
この記事では、コピペしてすぐ動く最小テンプレートを3種類用意した。PreToolUse(ツール実行前)、PostToolUse(ツール実行後)、Stop(セッション終了時)。全部bashスクリプト。全部10行前後。
hookの基本構造
どのhookも同じパターンで動く。
- Claude CodeがstdinにJSONを流し込む
- hookがJSONを読んで判断する
- 終了コードで結果を返す
終了コードの意味:
| exit code | 意味 |
|---|---|
0 |
問題なし。そのまま続行 |
1 |
警告。stderrの内容をClaudeにフィードバック |
2 |
ブロック。ツール実行を止める(PreToolUseのみ) |
stdoutにJSON({"decision":"approve"}等)を出すと、許可判定を上書きできる。stderrに書いた文字列はClaudeへのフィードバックになる。
1. PreToolUse テンプレート(ブロック用)
ツール実行の前に走る。危険な操作を止めたいときに使う。
~/.claude/hooks/block-template.sh:
#!/bin/bash
# PreToolUse: 特定コマンドをブロックするテンプレート
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$COMMAND" ] && exit 0
# ブロック条件(ここを書き換える)
if echo "$COMMAND" | grep -qE 'rm\s+-rf\s+(/|~|\$HOME)'; then
echo "BLOCKED: rm -rf on sensitive path" >&2
exit 2
fi
exit 0
exit 2がブロック。exit 0が許可。stderrに書いた理由はClaudeに伝わるので、Claudeが代替手段を考えてくれる。
応用例: git push --forceをブロック
if echo "$COMMAND" | grep -qE 'git\s+push\s+.*--force'; then
echo "BLOCKED: force-push detected. Use --force-with-lease instead." >&2
exit 2
fi
2. PreToolUse テンプレート(自動承認用)
安全だと分かっているコマンドの許可プロンプトを消す。Auto Modeじゃなくても、特定のコマンドだけ自動で通したいときに便利。
~/.claude/hooks/approve-template.sh:
#!/bin/bash
# PreToolUse: 安全コマンドを自動承認するテンプレート
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$COMMAND" ] && exit 0
# 承認条件(ここを書き換える)
BASE=$(echo "$COMMAND" | awk '{print $1}')
case "$BASE" in
cat|ls|head|tail|grep|find|wc|tree|pwd)
echo '{"decision":"approve","reason":"Read-only command"}'
exit 0
;;
esac
# マッチしなければ何もしない(通常の許可フローへ)
exit 0
stdoutに{"decision":"approve"}を出すのがポイント。これでClaude Codeの許可プロンプトがスキップされる。
3. PostToolUse テンプレート
ツール実行の後に走る。結果を検証して問題があればClaudeに伝える。
~/.claude/hooks/post-check-template.sh:
#!/bin/bash
# PostToolUse: 実行結果を検証するテンプレート
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
# ファイル操作以外はスキップ
[ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ] && exit 0
# 検証(ここを書き換える)
EXT="${FILE_PATH##*.}"
case "$EXT" in
py)
ERR=$(python3 -m py_compile "$FILE_PATH" 2>&1)
if [ $? -ne 0 ]; then
echo "SYNTAX ERROR: $ERR" >&2
exit 1
fi
;;
sh)
ERR=$(bash -n "$FILE_PATH" 2>&1)
if [ $? -ne 0 ]; then
echo "SYNTAX ERROR: $ERR" >&2
exit 1
fi
;;
json)
ERR=$(jq empty "$FILE_PATH" 2>&1)
if [ $? -ne 0 ]; then
echo "SYNTAX ERROR: $ERR" >&2
exit 1
fi
;;
esac
exit 0
exit 1で警告を返す。ブロックはしない(PostToolUseではexit 2は使えない。実行はもう終わっている)。stderrの内容がClaudeにフィードバックされ、自動的に修正を試みてくれる。
4. Stop テンプレート
セッション終了時に走る。ログ記録やクリーンアップに使う。
~/.claude/hooks/stop-template.sh:
#!/bin/bash
# Stop: セッション終了時の処理テンプレート
INPUT=$(cat)
REASON=$(echo "$INPUT" | jq -r '.stop_reason // "unknown"' 2>/dev/null)
TIMESTAMP=$(date -Iseconds)
# ログ記録(ここを書き換える)
LOG="$HOME/.claude/session-log.txt"
echo "[$TIMESTAMP] Session ended: reason=$REASON" >> "$LOG"
# 異常終了の通知(オプション)
if [ "$REASON" != "user" ] && [ "$REASON" != "normal" ]; then
echo "WARNING: Unexpected stop reason: $REASON" >&2
fi
exit 0
Stopフックでは終了コードの意味が変わる。何を返してもセッション終了は止まらない。stderrに書いた内容はログとして残る。
settings.jsonへの登録
hookファイルを書いたら~/.claude/settings.jsonに登録する。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/block-template.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/post-check-template.sh"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/stop-template.sh"
}
]
}
]
}
}
matcherはツール名でフィルタする。"Bash"ならBashツールだけ、""(空文字)なら全ツールに反応する。Edit、Write、Readなど任意のツール名を指定できる。
テスト方法
hookを登録する前に、ターミナルで単体テストできる。
# PreToolUseのテスト(rm -rfをブロックするか?)
echo '{"tool_input":{"command":"rm -rf /"}}' | bash ~/.claude/hooks/block-template.sh
echo $?
# → 2(ブロック)
# 安全なコマンドは通るか?
echo '{"tool_input":{"command":"ls -la"}}' | bash ~/.claude/hooks/block-template.sh
echo $?
# → 0(許可)
# PostToolUseのテスト(壊れたJSONを検知するか?)
echo '{"broken":}' > /tmp/test.json
echo '{"tool_input":{"file_path":"/tmp/test.json"}}' | bash ~/.claude/hooks/post-check-template.sh
echo $?
# → 1(警告)
echo JSON | bash hook.sh; echo $?のパターンを覚えておけば、どんなhookでもテストできる。
hookを自動生成する
テンプレートのコピペで足りないとき。「やりたいこと」を日本語で伝えると、hookを自動生成してくれるツールがある。
npx cc-safe-setup --create "git commitする前にlintを実行"
442個のサンプルhookから似たものを探してインストールもできる:
# サンプル一覧
npx cc-safe-setup --examples
# 特定のサンプルをインストール
npx cc-safe-setup --install-example credential-exfil-guard
hookの設計思想から実戦パターンまで体系的にまとめた本: Claude Code 実戦ガイド
テンプレートは4つ。PreToolUseのブロック、PreToolUseの自動承認、PostToolUseの検証、Stopのログ記録。この4パターンを押さえておけば、大抵の要件はカバーできる。まずは1つコピペして動かしてみてほしい。