Claude Code hooks 自動化 開発者ツール ワークフロー CLAUDE.md

Claude Code Hooks実践ガイド: すぐ使えるサンプル10本付き

The Prompt Shelf ·

Claude Code hooksを使うと、AIのライフサイクルの特定のタイミングで任意のコードを実行できる。ファイル編集のたびに自動フォーマット、実行前の危険なシェルコマンドのブロック、Claudeが入力待ちの時の通知、コンパクション後のコンテキスト再注入 — hooksでこれら全てが確実に実行できる。

キーワードは「確実に」だ。hooksなしでは、フォーマッターを実行すること、特定のパターンを避けること、長いセッション後のコンテキスト再ロードをClaudeが「覚えている」ことに頼ることになる。Hooksはその依存を取り除く。条件なく、毎回実行される。

このガイドでhooksシステム全体を解説し、自分のプロジェクトに今すぐ導入できる10のパターンを提供する。

Hooksの仕組み

Hooksは設定ファイルに定義されたシェルコマンド(またはHTTPエンドポイント、LLMプロンプト)だ。Claude Codeが特定のライフサイクルイベントに達すると、対応するhooksを起動し、JSONデータをstdinに渡して、stdout・stderr・終了コードからレスポンスを読み取る。

終了コードが次の動作を制御する:

  • 終了コード0 — 処理を続行。stdoutに書かれた内容がClaudeのコンテキストに注入される(UserPromptSubmitSessionStartイベント)。
  • 終了コード2 — 処理をブロック。stderrに理由を書けば、Claudeがフィードバックとして受け取り調整する。
  • その他の終了コード — 処理は続行されるが、stderrはサイレントにログされる。Ctrl+Oでこのメッセージを確認できる。

より細かい制御のために、終了コードだけでなく終了コード0でstdoutにJSONオブジェクトを書くこともできる。

Hookイベントリファレンス

2026年時点で、Claude Codeは25のhookイベントをサポートしている:

イベント起動タイミング
SessionStartセッションの開始または再開時
SessionEndセッションの終了時
UserPromptSubmitプロンプトを送信した時(Claudeが処理する前)
PreToolUseツール呼び出しの実行前(ブロック可能)
PostToolUseツール呼び出しの成功後
PostToolUseFailureツール呼び出しの失敗後
PermissionRequest権限ダイアログが表示される時
PermissionDeniedツール呼び出しが自動分類器に拒否された時
NotificationClaudeがNotificationを送る時
StopClaudeが応答を完了した時
StopFailureAPIエラーでターンが終了した時
SubagentStartサブエージェントが生成された時
SubagentStopサブエージェントが完了した時
TaskCreatedTaskCreateでタスクが作成された時
TaskCompletedタスクが完了マークされた時
TeammateIdleエージェントチームのメンバーがアイドルになった時
InstructionsLoadedCLAUDE.mdやルールファイルが読み込まれた時
ConfigChangeセッション中に設定ファイルが変更された時
CwdChanged作業ディレクトリが変更された時
FileChanged監視ファイルがディスク上で変更された時
WorktreeCreateworktreeが作成される時
WorktreeRemoveworktreeが削除される時
PreCompactコンテキストのコンパクション前
PostCompactコンテキストのコンパクション完了後
ElicitationMCPサーバーがユーザー入力を要求した時
ElicitationResultユーザーがMCP elicitationに応答した後

最もよく使われるイベントはPreToolUsePostToolUseSessionStartStopNotificationだ。

設定

Hooksは設定ファイルのhooksブロックに書く。2つの場所:

// ~/.claude/settings.json — 全プロジェクトに適用
// .claude/settings.json — このプロジェクトのみ、gitにコミット可能

基本的な構造:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here"
          }
        ]
      }
    ]
  }
}

各イベントはhookグループの配列を取る。各グループにはmatcher(ツール名に対する正規表現、またはイベント固有のフィールド)とhooks配列がある。Claude Code内で/hooksを使って設定済みのhooksを確認し、正しく表示されているか確認できる。

実世界で使える10のHookパターン

1. ファイル編集のたびに自動フォーマット

全てのファイル編集後にPrettierを自動実行。Claudeの設定は不要。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

なぜ|| trueが必要か? Prettierは非サポートのファイルタイプ(.shなど)でゼロ以外の終了コードを返す。|| trueがないと、JS以外のファイルへの全ての編集がhookに阻まれる。サポートされていないタイプはサイレントに無視される。

Pythonならビルド部分をBlackに交換する:

{
  "type": "command",
  "command": "jq -r '.tool_input.file_path' | xargs -I{} sh -c 'echo {} | grep -q \"\\.py$\" && black {} 2>/dev/null || true'"
}

2. 危険なシェルコマンドをブロック

取り返しのつかない損害を与えるコマンドをClaudeが実行するのを防ぐ。全てのBashツール呼び出しの前に実行される。

.claude/hooks/guard-bash.shに保存:

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

BLOCKED_PATTERNS=(
  "rm -rf /"
  "rm -rf \*"
  "git push --force"
  "git push -f"
  "DROP TABLE"
  "DROP DATABASE"
  "chmod -R 777"
  "> /dev/sda"
)

for pattern in "${BLOCKED_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qi "$pattern"; then
    echo "Blocked: command matches dangerous pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0

登録方法:

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

重要: ここでのexit 2bypassPermissionsモードでもブロックする。Hooksは権限設定を変更しても回避できないルールを強制できる。

3. 機密ファイルを編集から保護する

Claudeが.env、lockファイル、.git/内のものを触れないようにブロックする。Claudeが理由を受け取り、アプローチを調整する。

#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED=(
  ".env"
  ".env.local"
  ".env.production"
  "package-lock.json"
  "yarn.lock"
  "pnpm-lock.yaml"
  ".git/"
  "*.pem"
  "*.key"
)

for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH is a protected file. Do not modify it." >&2
    exit 2
  fi
done

exit 0
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

4. Claudeが待機中にデスクトップ通知を送る

ターミナルを監視し続けなくていい。Claudeが入力を待っている時にネイティブ通知を受け取る。

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Linux向け:

{
  "type": "command",
  "command": "notify-send 'Claude Code' 'Needs your attention'"
}

macOSでの注意: osascriptはScript Editor経由でルーティングされるため、通知の権限が必要だ。何も表示されない場合は、Terminalで一度手動でコマンドを実行し、システム設定 > 通知 > Script Editorで通知を有効にする。

5. コンパクション後にコンテキストを再注入する

Claudeのコンテキストウィンドウが埋まると、コンパクションが会話を要約する。重要な詳細が失われる可能性がある。このhookはコンパクション直後に起動し、Claudeに覚えておいてほしいものを再注入する。

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Context restored after compaction. Key constraints: use Bun (not npm), run bun test before every commit, never modify src/generated/. Current branch: $(git branch --show-current)'"
          }
        ]
      }
    ]
  }
}

コマンドがstdoutに書いた内容はClaudeのコンテキストにシステムリマインダーとして追加される。動的にもできる — ファイルから読み取る、git log --oneline -5を実行する、環境ステータスを出力するなど。このhookはcompactイベントでのみ起動し、通常のセッション開始では起動しない。

6. 関連するファイル変更後にテストを実行する

Claudeがソースファイルを編集した後に自動でテストスイートを実行する。Claudeに覚えさせなくても、即座にリグレッションを検出できる。

#!/bin/bash
# .claude/hooks/run-tests.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# ソースファイルのみ実行。テスト自体や設定ファイルは除外
if echo "$FILE_PATH" | grep -qE '\.(ts|tsx|js|jsx|py)$' && \
   ! echo "$FILE_PATH" | grep -qE '\.(test|spec)\.' && \
   ! echo "$FILE_PATH" | grep -q '__tests__'; then
  
  echo "Running tests for changed file..." >&2
  
  if ! npm test --silent 2>&1 | tail -5; then
    echo "Tests failed after editing $FILE_PATH" >&2
    exit 2
  fi
fi

exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "timeout": 120,
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests.sh"
          }
        ]
      }
    ]
  }
}

timeout: 120に注意 — デフォルトのhookタイムアウトは10分(600秒)だが、hook単位で調整できる。

7. コミットメッセージのフォーマットを強制する

Conventional Commitsフォーマットに従っていないgit commit呼び出しをブロックする。ifフィールド(Claude Code v2.1.85以降が必要)を使って、他のBashコマンドごとにプロセスを起動せず、gitコミットのみにhookを絞り込む。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git commit*)",
            "command": "INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r '.tool_input.command'); if echo \"$CMD\" | grep -q 'git commit'; then MSG=$(echo \"$CMD\" | grep -oP '(?<=-m \")[^\"]+'); if ! echo \"$MSG\" | grep -qP '^(feat|fix|chore|docs|style|refactor|test|perf|ci)(\\(.+\\))?: .+'; then echo \"Commit message must follow Conventional Commits format: type(scope): description\" >&2; exit 2; fi; fi; exit 0"
          }
        ]
      }
    ]
  }
}

可読性のために、ロジックをインラインに書かずスクリプトファイルに移すことを推奨する。

8. 全ツール使用をログファイルに記録する

セッション中にClaudeが使用した全ツールの永続的なログを保持する。複雑なタスクでのデバッグ、監査、Claudeの動作の把握に便利だ。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{ts: now | todate, tool: .tool_name, input: .tool_input}' >> ~/.claude/tool-audit.log"
          }
        ]
      }
    ]
  }
}

空のmatcherは「全ツール使用で起動」を意味する。ログフォーマットは1行1JSONオブジェクト(JSONL)で、grepやjqへの受け渡しが簡単だ。

最近のツール呼び出しを表示:

tail -20 ~/.claude/tool-audit.log | jq '.tool + ": " + (.input | tostring)'

9. Plan Modeの終了を自動承認する

Plan Modeを使う時、ClaudeはプランをプレゼントしてからExecuteを始める前に承認を待つ。常に承認する場合は、PermissionRequest hookでダイアログをスキップできる。

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "ExitPlanMode",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
          }
        ]
      }
    ]
  }
}

matcherはExitPlanModeにスコープを絞ること。ここで空のmatcherを使うと、ファイル書き込みやシェルコマンドを含む全ての権限プロンプトが自動承認される。

10. LLMを使ってタスク完了を確認する

Claudeが完了サインを出した時、type: "prompt"Stop hookを使ってLLMに全タスクが本当に完了しているか確認させる。不足があればClaudeが理由を受け取って作業を続ける。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Review the conversation. Are all tasks requested by the user fully complete, including tests and documentation? If anything is missing, respond with {\"ok\": false, \"reason\": \"what needs to be done\"}. If everything is done, respond with {\"ok\": true}."
          }
        ]
      }
    ]
  }
}

無限ループ防止: Stop hookがok: falseを返すと、ClaudeはSトップした後作業を続けて新たなStopイベントを発生させる。ループを防ぐには、stdinからstop_hook_activeフィールドをパースして、trueならexit 0する。commandhooksの場合:

#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
# 残りのロジック

Prompt hooksはこれを自動で処理する — stop_hook_activeがfalseの時のみ起動する。

コマンドhookを超えて

上記の例の多くは"type": "command" — シェルスクリプトを使っている。しかし知っておく価値のある3つの別のhookタイプがある:

type: "prompt" — プロンプトとhookの入力をClaudeモデル(デフォルトでHaiku)に送る。{"ok": true}または{"ok": false, "reason": "..."}を返す。正規表現では判断できない場合に使う。例10を参照。

type: "agent" — ツールアクセスを持つフルサブエージェントを生成する。エージェントはファイルを読んだり、コマンドを実行したり、テスト出力を確認したり、マルチステップの判断をしてから結果を返せる。type: "prompt"だけでは不十分で、コードベースの実際の状態を検査する必要がある時に使う。

type: "http" — URLにイベントデータをPOSTする。エンドポイントはコマンドhookがstdinで受け取るのと同じJSONを受け取り、HTTPレスポンスボディで結果を返す。共有監査サービスやチーム全体の強制エンドポイントに便利だ。

よくある落とし穴

シェルプロファイルの汚染。 Claude Codeはhookコマンド実行前に~/.zshrcまたは~/.bashrcを読み込む。プロファイルに無条件のechoステートメントがあると、その出力がhookのJSON出力の前に追加されてパースが壊れる。echoをインタラクティブシェルのチェックで囲む:

if [[ $- == *i* ]]; then
  echo "Shell ready"
fi

絶対パス。 HookコマンドはClaudeの環境で作業ディレクトリ内で実行される。Hookスクリプトが$PATHにない場合は、絶対パスまたは$CLAUDE_PROJECT_DIRを使う:

"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/my-hook.sh"

並列hook実行。 複数のhookが同じイベントにマッチすると、全て並列で実行される。2つのPreToolUse hookが両方ともツールの入力を(updatedInputを使って)変更しようとすると、最後に完了したものが勝つ — 非決定的に。複数のhookが同じツールの入力を書き換えないようにする。

PostToolUseは元に戻せない。 ツールはすでに実行されている。PostToolUseは副作用(フォーマット、ログ)に使い、強制にはPreToolUseを使う。

PermissionRequestはヘッドレスモードで起動しない。 -p(非インタラクティブ)でClaudeを実行している場合は、自動権限判断にPreToolUseを代わりに使う。

Hooksのデバッグ

Hookが起動しない、または期待通りに動かない時:

  1. Claude Code内で/hooksを実行 — 正しいイベントの下に正しいmatcherでhookが表示されているか確認する。
  2. Ctrl+Oでverboseモードを切り替えてhookの出力をトランスクリプト内でインラインで確認する。
  3. サンプルJSONをパイプしてhookを手動でテスト:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"session_id":"test","cwd":"/tmp"}' | ./.claude/hooks/my-hook.sh
echo "Exit code: $?"
  1. 完全な実行詳細にはclaude --debugを実行する。どのhookがマッチしたかも含む。

次のステップ

上記の10のパターンが最も一般的なユースケースをカバーしている。Hooksシステムはずっと深い — HTTPhooks、エージェントhooks、worktreehooks、MCPエリシテーションhooks、完全な入出力スキーマはHooksリファレンスにドキュメント化されている。

コミュニティからの使用可能な例はkaranb192/claude-code-hooksリポジトリが成長中で、ブックマーク価値がある。

Claude Codeのセットアップをより広く整えたいなら、ギャラリーに本番コードベースから学べる数百の実際のCLAUDE.mdとAGENTS.md設定がある。

More from the blog

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules