MF Blogs 便利ツール
Claude CodeのHooksが開発フローを自動制御するイメージの抽象図

記事

Claude CodeのHooksで開発ワークフローを自動化する方法

Claude CodeのHooksの仕組みとイベント種別、settings.jsonでの設定例、PreToolUseで危険コマンドをブロックする方法、PostToolUseでlintを自動実行する方法、JSON出力でClaudeへフィードバックを返す方法、安全に運用するためのチェックリストまでを公式仕様に沿って整理します。

0:00 0:00

CLAUDE.mdは「お願い」、Hooksは「強制」です。「.envを読ませない」「rm -rfを実行させない」「編集後に必ずlintを走らせる」のように、確実に守らせたい挙動はCLAUDE.mdではなくHooksで実装します。本記事ではClaude CodeのHooksの仕組み・イベント種別・設定例・安全な運用方法を、公式仕様に沿って整理します。

結論:「守らせたい挙動」はHooksに寄せる

CLAUDE.mdに「rm -rfは禁止」と書いてもアドバイザリーで、AIが文脈次第で破ることがあります。Hooksは決定論的に動作するため、ルールを守らせたい場面では一段上の信頼性を得られます。具体的にはこういう使い分けです。

  • CLAUDE.md:書き方ガイド、命名規則、設計の好み(やや破ってもいい)
  • Hooks:実行可否の判定、ログ整理、自動lint、契約違反のブロック(破られたら困る)

ただしHooksは強力なぶん、設定ミスが連鎖事故になりやすい仕組みでもあります。安全運用のポイントは記事末尾のチェックリストに集約しました。CLAUDE.md側の書き方は「CLAUDE.mdテンプレート完全版」、セキュリティ全体像は「Claude Codeを使うときのセキュリティチェックリスト」に任せます。

Hooksの全体像:3つのカデンス

公式ドキュメントでは、Hooksイベントを発火タイミングで整理しています。

カデンス主なイベント
セッション単位SessionStart / SessionEnd / Setup
ターン単位UserPromptSubmit / Stop / StopFailure
ツール呼び出しごとPreToolUse / PostToolUse / PermissionRequest / PermissionDenied
非同期Notification / SubagentStart / SubagentStop / PreCompact / FileChanged / CwdChanged ほか

最初に触るべきは次の4つです。

  1. PreToolUse:ツール実行。ブロック・許可・差し戻しが可能(最も強力)
  2. PostToolUse:ツール実行。lintや通知などの後処理(ブロック不可)
  3. UserPromptSubmit:ユーザー入力の送信前。プロンプトの拒否やコンテキスト追加が可能
  4. SessionStart:セッション開始時。git statusやブランチ名などをコンテキスト注入

PreToolUsepermissionDecisionallow / deny / ask / deferを返せます。「破壊的コマンドをdeny」「読み取り専用コマンドをallowで自動承認」のような自動化が組めます。

最小設定:settings.jsonにJSONで書く

Hooksは設定ファイルに JSON で記述します。配置場所で適用範囲が変わります。

場所適用範囲チーム共有
~/.claude/settings.json自分のすべてのプロジェクトしない
.claude/settings.jsonこのプロジェクトGit管理して共有
.claude/settings.local.jsonこのプロジェクトgitignore推奨
Managed settings組織全体強制適用

最小構造はこうなります。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

matcherは対象ツール名のパターンで、"Bash"のような完全一致、"Edit|Write"のパイプ区切り、"mcp__memory__.*"のような正規表現も使えます。"*"または省略で全マッチです。

ハンドラのtypecommand(シェル)、httpmcp_toolpromptagentの5種類。最も使うのはcommandです。

例1:rm -rfをブロックするPreToolUse

最頻出のユースケースから入ります。.claude/hooks/block-rm.shに次のスクリプトを置きます。

#!/bin/bash
# 標準入力にイベントJSONが来る
COMMAND=$(jq -r '.tool_input.command // empty')

if echo "$COMMAND" | grep -qE '(^|[^a-zA-Z])rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "rm -rf 系コマンドはHookでブロックされています。"
    }
  }'
  exit 0
fi
exit 0

スクリプトは実行権限を付けます。

chmod +x .claude/hooks/block-rm.sh

.claude/settings.jsonにHookを登録します。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

ifフィールドは権限ルールと同じ構文で「rmで始まるBash実行のときだけ走る」というプレフィルタです。Hook側でもう一度パターンを照合するのは、rm -rfをワンライナーに混ぜるbash -c 'cd /tmp && rm -rf foo'のようなケースを取りこぼさないためです。多重防御が基本です。秘密情報の3層防御は「Claude Codeに秘密情報を渡さないための実践ルール」で扱っています。

例2:Edit/Writeの後にlintを自動実行するPostToolUse

ファイル編集の直後にlintを走らせると、AIの「壊れたコードを残すまま次に進む」を抑止できます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(*.ts) | Edit(*.tsx) | Write(*.ts) | Write(*.tsx)",
            "command": "/usr/local/bin/eslint",
            "args": ["${tool_input.file_path}", "--fix"]
          }
        ]
      }
    ]
  }
}

PostToolUseはブロック不可ですが、終了コード2を返すとstderrの内容がClaudeに次の入力として渡されます。lintエラーをClaudeに食わせて自動修正させる流れが組めます。lint結果が大量に出るときはgreperror行に絞ると、コンテキストを圧迫しません。

CLAUDE.mdの「アドバイザリーな規約」とHooksの「確定的なチェック」の使い分けは「Claude Codeにコード規約を守らせるための書き方」で扱っています。

例3:セッション開始時にコンテキストを注入する

朝にClaude Codeを起動した直後、毎回「現在のブランチは?」「未コミットは?」と聞き直すのは無駄です。SessionStartHookで自動的に状況を渡します。

#!/bin/bash
# .claude/hooks/load-context.sh
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
DIRTY=$(git status --short 2>/dev/null | wc -l | tr -d ' ')

jq -n --arg branch "$BRANCH" --arg dirty "$DIRTY" '{
  hookSpecificOutput: {
    hookEventName: "SessionStart",
    additionalContext: ("Current branch: " + $branch + "\nUncommitted files: " + $dirty)
  }
}'

設定はこうなります。

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|resume",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/load-context.sh"
          }
        ]
      }
    ]
  }
}

additionalContextに書いた文字列がそのままClaudeのコンテキストに追加されます。GitHub IssueのオープンPR数、過去24時間のSentryエラー件数など、毎回手で貼るのが面倒な「短いサマリ」をここに集約すると体感が変わります。コンテキスト消費の設計は「Claude Codeに必要な情報だけ渡すコンテキスト設計」を参照してください。

入出力仕様の要点

Hookに渡される共通フィールド

command型Hookは標準入力にJSONを受け取ります。

{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/directory",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" }
}

jq -r '.tool_input.command'のように取り出して判定します。

終了コードの意味

Exit Code意味JSON処理
0成功標準出力のJSONを解釈
2ブロックstderrの内容をClaudeに返す
その他非ブロックエラー無視(デバッグログのみ)

JSON出力で返せる主なフィールド

フィールド用途
continuefalseで会話を停止
stopReasoncontinue: false時の理由
systemMessage警告メッセージ表示
decisionblockallow
hookSpecificOutput.permissionDecisionPreToolUse専用:allowdenyaskdefer
hookSpecificOutput.additionalContextClaudeに追加のコンテキストを渡す(SessionStart等)

危険な自動化と回避策

Hooksは強力ですが、設定ミスがそのまま事故になります。AIコーディング特有のリスクを5つ整理します。

ミス何が起きる回避
PermissionRequestを全部Allow返し任意の破壊コマンドが通るifで対象を絞る、Bash(git *)のように許可は最小限
PostToolUseでgit pushまで走らせるレビューなしでpushが発生PostToolUseは読み取り系・検査系に限定する
PreToolUse内でAPIキーをechoプロンプトに混入してログに残るHookスクリプトで秘密情報を出力しない
一般的すぎるmatcher: "*"全ツールで余計な処理が走り遅延matcherは最も狭い範囲に
自作Hookでexit 2を多用Claudeが無限リトライexit 2の前にメッセージを明確にし、再試行ループに入らないよう設計

bypassPermissionsモードと組み合わせる場合は、隔離環境(VM・コンテナ)でしか使わない原則を貫いてください。本番ターミナルや/配下まで触れる端末でHookとbypassを組み合わせるのは絶対に避けます。

/hooksメニューと管理

Claude Code内で/hooksと入力すると、現在有効なHooksの一覧をイベントごと・ソース別(User/Project/Local/Plugin/Session/Built-in)に確認できます。読み取り専用UIなので、編集はsettings.jsonを直接触ります。

Hooksをまるごと無効化したい場合はsettings.jsonに次を入れます。

{
  "disableAllHooks": true
}

組織レベルでは管理者がallowManagedHooksOnlyを使い、ユーザー・プロジェクト・プラグインのHooksを禁止する設計も取れます(Managed settingsの仕組みで強制適用)。チーム導入時のルール設計は「チームでClaude Codeを導入するときに最初に決めるルール」で扱っています。

安全運用チェックリスト

  • PreToolUseifで対象を絞り、許可範囲を最小化した
  • rm -rf git push --force npm publishをブロック対象に含めた
  • PostToolUseは読み取り・検査系のみで、書き込みや送信を行わない
  • Hookスクリプトに実行権限を付け、set -euo pipefailを入れた
  • ${CLAUDE_PROJECT_DIR}を使い、絶対パス埋め込みを避けた
  • bypassPermissionsは隔離環境でのみ使う運用にした
  • disableAllHooksの一時無効化手段をチームで共有した
  • Hookスクリプトに秘密情報を出力する箇所がない
  • .claude/settings.jsonをGit管理し、Hookの追加・変更はPRレビュー対象にした
  • /hooksメニューで意図したHookだけが有効になっていることを確認した

次に読むおすすめ記事: