Claude Code の hooks 実践 — ツール実行の前後でフォーマット・ブロック・通知を自動化する

Claude Code の hooks は、ツール呼び出しやセッションの節目に自前のスクリプトを差し込む仕組み。PreToolUse で危険なコマンドを止め、PostToolUse で保存時に自動フォーマットし、Stop で完了通知を出す——この 3 つを settings.json とシェルスクリプトだけで組む手順を、2026-06 時点の仕様で具体的にまとめます。

Claude Code を使い込むと、「毎回これは止めてほしい」「編集後に必ずフォーマットしてほしい」「長いタスクが終わったら通知してほしい」といった、エージェントの動きに割り込みたい場面が出てきます。これを CLAUDE.md のお願いベースではなく、確実に実行されるフックとして組めるのが hooks です。本記事では実運用で効く 3 パターンを、コピペできる形で解説します。

※ hooks の挙動は更新が早い領域です。本記事は 2026-06 時点 の仕様に基づきます。フィールド名や挙動は公式ドキュメントで都度確認してください。

hooks とは何か

hooks は、Claude Code のライフサイクル上の決まった地点で、あなたのコマンドを実行する仕組みです。CLAUDE.md の指示が「Claude にお願いする(従わないこともある)」のに対し、hooks は ハーネスが必ず実行する点が決定的に違います。「絶対に守らせたいルール」は hooks に置くのが正解です。

よく使うイベントは次の通り。

イベント発火タイミングmatcherブロック可否
PreToolUseツール呼び出しの直前ツール名可(実行を止められる)
PostToolUseツール成功の直後ツール名不可(観測・後処理のみ)
UserPromptSubmitプロンプト送信時なし
StopClaude の応答が終わったときなし
SessionStartセッション開始 / 再開startup不可

PreToolUsePostToolUse が最もよく使われます。前者は 実行前に検査して止められる、後者は 起きた後に反応する——この非対称性を押さえておくと設計を間違えません。

設定の置き場所と構造

hooks はプロジェクト共有なら .claude/settings.json、自分専用なら ~/.claude/settings.json に書きます。両方ある場合はプロジェクト側が優先されます。構造はこうです。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-bash.sh",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

matcher はツール名("Bash""Edit|Write" の OR、"*" の全マッチ)。command の中の ${CLAUDE_PROJECT_DIR} はプロジェクトルートに展開されるので、パスをハードコードせずに済みます。

スクリプトには stdin から JSON が渡ってきます。中身はこんな形です。

{
  "session_id": "abc123",
  "cwd": "/path/to/project",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" }
}

パターン1: PreToolUse で危険なコマンドを止める

まず効果が分かりやすいのが、破壊的な Bash コマンドのブロックです。.claude/settings.json に上の PreToolUse 設定を入れ、スクリプトを置きます。

#!/bin/bash
# .claude/hooks/guard-bash.sh
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)

if echo "$COMMAND" | grep -qE 'rm -rf|git push --force|: *\(\)'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "破壊的コマンドをブロックしました(guard-bash.sh)"
    }
  }'
else
  exit 0   # 判断しない → 通常の権限フローに任せる
fi

permissionDecisionPreToolUse 専用のフィールドで、"allow" / "deny" / "ask" / "defer" を返せます。"deny" を返すとそのツール呼び出しは実行されず、理由が Claude にフィードバックされます。何も判断しないときは exit 0 で抜けるのがポイント——ここで "allow" を返すと通常の権限確認まで飛ばしてしまうので、止めたいときだけ "deny" を出すのが安全です。

パターン2: PostToolUse で編集後に自動フォーマット

ファイル編集のたびに手で整形するのは面倒です。PostToolUseEdit / Write を捕まえ、フォーマッタを走らせます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/format.sh
FILE=$(jq -r '.tool_input.file_path // empty' < /dev/stdin)
[ -z "$FILE" ] && exit 0

case "$FILE" in
  *.ts|*.tsx|*.js|*.jsx|*.json|*.css|*.astro)
    npx prettier --write "$FILE" >/dev/null 2>&1 ;;
esac
exit 0

PostToolUseすでに起きたことを取り消せないため、ここでブロックを試みても意味がありません。やるのは「整える・記録する・知らせる」だけ。フォーマット結果はそのまま次のツール呼び出しに反映されるので、Claude が見るファイルも整形済みになります。

パターン3: Stop で完了を通知する

長いタスクを投げて別作業をしていると、いつ終わったか分かりません。Stop(応答完了)で macOS の通知を出します。

{
  "hooks": {
    "Stop": [
      { "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/notify.sh" } ] }
    ]
  }
}
#!/bin/bash
# .claude/hooks/notify.sh
osascript -e 'display notification "タスクが完了しました" with title "Claude Code"' 2>/dev/null
exit 0

Stop には matcher がありません(ツールに紐づかないため)。通知だけなら exit 0 で十分です。Linux なら notify-send、リモートなら Slack の Incoming Webhook に curl する形に差し替えられます。

exit code の意味を取り違えない

hooks のハマりどころは exit code の解釈です。ここだけは正確に。

  • exit 0 — 成功。stdout の JSON が処理される(permissionDecision 等が効く)
  • exit 2 — ブロッキングエラー。stdout は無視され、stderr が Claude にエラーとして渡る。手早く止めたいだけなら、JSON を組み立てず echo "理由" >&2; exit 2 でも PreToolUse はツールを止められる
  • それ以外 — 非ブロッキングエラー。警告だけ出して処理は続行

つまり「JSON で丁寧に deny を返す」方法と「stderr に出して exit 2」の 2 通りがあり、後者は手軽な代わりに細かい制御ができません。用途で選びます。

落とし穴

  • PostToolUse でブロックしようとする。後処理イベントなので止められません。検査して止めたいなら PreToolUse に置く
  • 何も判断しないのに "allow" を返すPreToolUse で常時 allow を返すと通常の権限確認を毎回スキップしてしまう。止めたいときだけ deny、それ以外は exit 0
  • jq 前提なのに環境に無い。hooks スクリプトは最小依存で書く。jq が無い環境向けには素の grep / sed でフォールバックを用意する
  • timeout を設定せず重い処理を回す。フォーマットやテストを同期実行すると体感が重くなる。重い検査は timeout を短く切るか、非同期に逃がす
  • settings.json の JSON が壊れている。1 文字のカンマ抜けで hooks 全体が読み込まれない。編集後は cat .claude/settings.json | jq . で構文確認する癖をつける

まとめ

hooks は「CLAUDE.md のお願い」を「ハーネスの強制」に格上げする仕組みです。まずは PreToolUse で危険コマンドを止める / PostToolUse で保存時フォーマット / Stop で通知 の 3 つから始めれば、それだけで日々の安心感と手数がはっきり変わります。チームで共有するルールは .claude/settings.json に、個人の好みは ~/.claude/settings.json に——この置き分けも最初に決めておくと後で迷いません。

自動承認・自動拒否の境界を自然言語で引く設計は、Claude Code の auto mode hard_deny 実用設計 も併せてどうぞ。

参考