メインコンテンツへスキップ
ブログ一覧

Claude Code Hooks実践ガイド — 5種ハンドラー×24イベントで実現する確定的ワークフロー自動化

(更新: 2026年04月25日)
Claude CodeHooksワークフロー自動化開発環境

Hooksとは何か——LLMを経由しない唯一の自動化レイヤー

Claude Codeの自動化機能は増え続けている。Skills、CLAUDE.md、カスタムサブエージェント……しかし、これらはすべてLLMの判断を経由する。「git pushの前に必ずテストを走らせたい」「機密ファイルの編集は絶対にブロックしたい」——そんな「確実に実行されなければ困る」処理には、唯一の確定的自動化手段であるHooksが最適解だ。

本記事では、2026年4月時点のライフサイクルイベントと4種のハンドラータイプを体系的に整理し、コピペで使える実用パターンを紹介する。

確定的実行 vs 確率的実行:設計判断フロー

Claude Codeの自動化手段は、「実行の確実性」で明確に分類できる。

  • Hooks — イベント発火時に無条件で実行される。LLMの判断を一切介さない
  • Skills — LLMが「使うかどうか」を判断する。プロンプトに合致すれば呼ばれるが、呼ばれない可能性もある
  • CLAUDE.md — LLMが「従うかどうか」を判断する。指示として読み込まれるが、遵守は確率的
  • サブエージェント — LLMが起動タイミングを判断する。複雑なワークフロー向き

設計判断のフローはシンプルだ。確実に実行すべき処理か? Yesなら迷わずHooks。LLMに判断を委ねたいならSkillsかCLAUDE.md。複雑な対話的ワークフローならサブエージェントを選ぶ。

Gitフックとの類似点と決定的な違い

Gitに馴染みがあれば、Hooksのメンタルモデルは直感的に理解できる。pre-commitはPreToolUseに、post-commitはPostToolUseに対応する。ただし決定的な違いがある——Hooksはツール単位の粒度で制御できる。「Bashツールの実行前だけ」「Writeツールで.envファイルに書き込む時だけ」といった細かな条件指定が可能だ。


4種のハンドラータイプ:適材適所の選び方

Command:シェルコマンド実行(基本形)

typeを省略した場合のデフォルト。最も使用頻度が高い。終了コードの意味は覚えておく必要がある——0で続行、2でブロック、それ以外は非ブロックエラー(処理は続行される)。

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": { "tool_name": "Bash" },
        "hook": {
          "type": "command",
          "command": "bash -c 'if echo \"$TOOL_INPUT\" | grep -q \"rm -rf /\"; then echo \"危険なコマンドをブロックしました\" >&2; exit 2; fi'"
        }
      }
    ]
  }
}

HTTP:外部エンドポイントへのPOST

urlフィールドにエンドポイントを指定する。イベントペイロードがJSON POSTされ、レスポンスボディがClaude Codeへのフィードバックになる。Slack通知やCI連携に最適だ。

json
{
  "hooks": {
    "Stop": [
      {
        "hook": {
          "type": "http",
          "url": "https://hooks.slack.com/services/T00/B00/xxxxx",
          "headers": { "Content-Type": "application/json" }
        }
      }
    ]
  }
}

Prompt:小型LLMに単発判定を委託

modelpromptを指定し、軽量なLLMに判定を委ねる。Commandの終了コード制御が不要な簡易判定に向く。ただし、LLMを経由する以上確定的実行ではなくなる点に注意が必要だ。正直、これをHooksと呼ぶべきかは議論の余地がある。

Agent:サブエージェントを起動(実験的)

複雑なワークフローを自動実行するための実験的ハンドラー。安定性は要検証であり、本番環境での採用は慎重に判断したい。


実用パターン集:イベント別コピペ設定例

PreToolUse:危険操作のブロックとバリデーション

matcherフィールドで対象ツールを絞り込み、条件に合致した場合にexit 2でブロックする。stderrに出力した内容がClaude Codeにフィードバックされるため、ブロック理由を伝えられる。

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": { "tool_name": "Bash" },
        "hook": {
          "type": "command",
          "command": "bash -c 'CMD=$(echo \"$TOOL_INPUT\" | jq -r .command // empty); if echo \"$CMD\" | grep -qE \"(rm -rf|git push --force|drop table)\"; then echo \"この操作はHooksによりブロックされました: $CMD\" >&2; exit 2; fi'"
        }
      },
      {
        "matcher": { "tool_name": "Write" },
        "hook": {
          "type": "command",
          "command": "bash -c 'FILE=$(echo \"$TOOL_INPUT\" | jq -r .file_path // empty); if echo \"$FILE\" | grep -qE \"\\.env$\"; then echo \".envファイルへの書き込みは禁止されています\" >&2; exit 2; fi'"
        }
      }
    ]
  }
}

PostToolUse:テスト自動実行とフォーマッター連携

Writeツールでファイルが書き込まれた後に、自動でlint/formatを実行するパターン。地味だが、これがあるだけでコードレビューのストレスが激減する。

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": { "tool_name": "Write" },
        "hook": {
          "type": "command",
          "command": "bash -c 'FILE=$(echo \"$TOOL_INPUT\" | jq -r .file_path // empty); case \"$FILE\" in *.ts|*.tsx|*.js|*.jsx) npx eslint --fix \"$FILE\" 2>/dev/null;; *.py) ruff format \"$FILE\" 2>/dev/null;; esac; exit 0'"
        }
      }
    ]
  }
}

SessionStart:環境セットアップの自動化

セッション開始時にNode.jsバージョンの切り替えやDocker起動を行う。毎回手動で実行していた手順をここに集約できる。

json
{
  "hooks": {
    "SessionStart": [
      {
        "hook": {
          "type": "command",
          "command": "bash -c 'source ~/.nvm/nvm.sh && nvm use 2>/dev/null; docker compose up -d 2>/dev/null; echo \"環境セットアップ完了\"'"
        }
      }
    ]
  }
}

Stop:レスポンス完了時のステージングpushとレポート

Claudeがレスポンスを完了した時に、作業中の変更をstashしたり、サマリーをSlackに通知するパターン。

json
{
  "hooks": {
    "Stop": [
      {
        "hook": {
          "type": "command",
          "command": "bash -c 'cd \"$CLAUDE_PROJECT_DIR\" && if [ -n \"$(git status --porcelain)\" ]; then git stash push -m \"claude-session-$(date +%Y%m%d-%H%M%S)\"; fi'"
        }
      }
    ]
  }
}

UserPromptSubmit:コンテキスト自動注入

ユーザーのプロンプト送信時に、プロジェクト固有のコンテキスト(直近のデプロイ状態など)を自動で注入できる。


設定ファイルの優先度とチーム運用

設定ファイルの優先度

Hooksの設定は以下の優先度で読み込まれる(上が最優先)。

  1. マネージドポリシー — 組織レベルで管理者が強制(最優先、個人設定で上書き不可)
  2. CLIフラグ — コマンドライン引数による指定
  3. ローカル.claude/settings.local.json.gitignore推奨)
  4. プロジェクト.claude/settings.json(リポジトリにコミット可能)
  5. グローバル~/.claude/settings.json(全プロジェクト共通)
  6. プラグイン / スキルフロントマター

チーム共有フック vs 個人フック

使い分けの基準は明確だ。

.claude/settings.jsonにコミットすべきフック:

  • 危険操作(rm -rfforce push)のブロック
  • lint/formatの自動実行
  • .envファイルへの書き込み防止

settings.local.jsonに留めるべきフック:

  • 個人のSlack通知
  • エディタ連携(VSCode / Cursor固有の設定)
  • ローカル環境固有のDocker起動スクリプト

組織レベルではマネージドポリシーにより、セキュリティ関連のフックを全メンバーに強制できる。個人がローカル設定で上書きしても、マネージドポリシーのフックは必ず実行される。


ハマりポイントと設計上の注意点

終了コードの罠:exit 1はブロックしない

これが最大の落とし穴だ。多くの開発者が「エラー = exit 1」と条件反射で書いてしまうが、Hooksにおいてexit 1は非ブロックエラーであり、処理は続行される。

なお、exit 2によるブロックが有効なのはPreToolUseなどの一部イベントに限られる。PostToolUse、Notification、SessionStartなどのイベントではexit 2を返してもブロックは無視され、処理が続行される。ブロック機能を前提とする場合は、対象イベントがexit 2に対応しているか確認すること。

bash
# NG: exit 1 → 処理は続行されてしまう
if echo "$CMD" | grep -q "rm -rf"; then
  echo "危険な操作です" >&2
  exit 1  # ブロックされない!
fi

# OK: exit 2 → PreToolUse等の対応イベントで処理がブロックされる
if echo "$CMD" | grep -q "rm -rf"; then
  echo "危険な操作です" >&2
  exit 2  # 正しくブロックされる
fi

stderrの扱いは終了コードによって異なる。exit 2(ブロック)の場合はstderrの内容がそのままエラーメッセージとしてClaude Codeに渡される。exit 1などの非ブロックエラーの場合は、stderrの1行目のみがトランスクリプトに表示される。詳細なエラー情報を伝えたい場合は1行目に要約を書くこと。

タイムアウトとパフォーマンス影響

Commandハンドラーにはデフォルトのタイムアウトがある。テストスイートの実行のような重い処理をPreToolUseに入れると、タイムアウトで意図しない挙動になることがある。重い処理はHTTPハンドラーに逃がすか、バックグラウンド実行を検討しよう。

Hooksを使うべきでないケース

万能に見えるHooksだが、以下のケースでは別の手段が適している。

  • LLMの出力内容に応じた条件分岐 → Skills
  • 複雑な対話的ワークフロー → サブエージェント
  • ドキュメント的な指示・コーディング規約 → CLAUDE.md
  • プロジェクト固有のコマンド体系 → Skills(スラッシュコマンド化)

まとめ

Claude Code Hooksは、LLMの確率的判断に頼らず「必ず実行される」唯一の自動化手段だ。4種のハンドラータイプと各種ライフサイクルイベントを理解すれば、危険操作の確実なブロックからCI連携、チーム全体のワークフロー標準化まで、あらゆる確定的自動化が実現できる。

まずはPreToolUseでの危険操作ブロックとPostToolUseでのlint自動実行から始めてみてほしい。この2つだけでも、Claude Codeとの協働体験が明確に変わるはずだ。

もっと読む他の技術記事も読む