SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-26

Claude Code hooksの仕組みと設定方法

Claude Code hooksはmodelの判断に関係なく実行されます。設定場所、発火するevent、exit status 2でtool callをキャンセルする仕組み、security上の注意点を解説します。

Claude Code の hook とは

Claude Code の hook は、Claude Code が自身のライフサイクル内の決められた時点で自動的に実行する shell command です。これが hook と rules file の本質的な違いです。CLAUDE.md 内の指示は助言であり、model はコンテキスト内の他の情報と照らして判断します。hook は code であり、model の判断に関係なく実行されます。formatter について 2 回伝えたにもかかわらず agent が実行を省略し続ける場合、より強い指示は必要ありません。hook が必要です。

仕組みは単純です。event name の下に command を settings file へ登録します。その event が発生すると、Claude Code は command を実行し、event data を JSON (JavaScript object notation) として標準入力 (stdin) に書き込みます。command はその data を読み取り、処理を実行して終了ステータスを返します。PreToolUse hook が exit status 2 を返すと、tool call は実行前にキャンセルされます。script が標準エラー出力 (stderr) に書き込んだ内容は、その理由として model に渡されます。

ここで使用している event name と field name は、2026 年 8 月に release 2.1.232 を基準として確認した Claude Code の hooks reference に基づいています。この仕様は頻繁に変更されるため、この文書を含め、blog post から JSON をコピーする前に使用している version の reference を確認してください。claude --version で自分の内容を表示できます。

フック設定の格納場所

フックは、設定ファイル内の JSON ブロックです。6 つの場所に定義でき、フックの適用範囲は設定ファイルの範囲になります。

  • ~/.claude/settings.json: 自分のマシン上のすべてのプロジェクト。他のユーザーには適用されません。
  • .claude/settings.json: 1 つのプロジェクト。リポジトリにコミットするため、クローンした全員にフックが適用されます。
  • .claude/settings.local.json: 1 つのプロジェクト。自分のマシンにだけ適用されます。
  • 管理ポリシー設定: 組織全体に適用され、管理者が設定します。
  • プラグイン内の hooks/hooks.json: プラグインが有効な間だけ適用されます。
  • Skill または subagent の frontmatter: そのコンポーネントがアクティブな間だけ適用されます。

これらのファイルにあるフックエントリは、互いに上書きするのではなく統合されます。プロジェクト設定ファイルを追加すると、ユーザー設定のフックが置き換えられるのではなく、そこに追加されます。そのため、1 つのイベントに複数のファイルから複数のフックを登録できます。"disableAllHooks": true を設定するとフックは無効になります。ただし、管理ポリシー設定のフックは例外です。その設定も管理設定に適用しない限り、実行され続けます。

セッション内で /hooks を実行すると、現在登録されているすべてのフックがイベント別に一覧表示されます。各フックのソースファイルと matcher も表示されます。このメニューは読み取り専用です。フックを変更するには、設定ファイルを編集します。通常は、再起動しなくてもファイルウォッチャーが編集内容を検出します。

Claude Code に存在する hook event

Release 2.1.232 では、SessionStart から SessionEnd までの 31 個の event が定義されています。compaction、subagent、worktree、設定ファイルに関する event が含まれます。サーバー運用で使用するのは、そのうち一部です。

  • PreToolUse: tool call の実行前です。処理をブロックできる唯一の event です。
  • PostToolUse: tool call が成功した後です。失敗時には PostToolUseFailure が発火します。すべての結果を取得する hook には、両方が必要です。
  • PermissionRequest: tool call に permission の判断が必要なときです。approval prompt が表示されるタイミングに該当します。
  • UserPromptSubmit: prompt を送信したときで、Claude が処理する前です。この hook が stdout に出力した内容は、model の context に追加されます。
  • SessionStart と SessionEnd: session の各終了時です。SessionStart は compaction の後にも、matcher の値 compact で発火します。
  • Stop: Claude が応答を完了したときです。完了した task ごとではなく、turn ごとに 1 回発火します。

すべての group には matcher があり、どの発生時に hook を実行するかを決めます。tool event では tool name で絞り込むため、"Edit|Write" はファイル編集時だけ発火し、それ以外では発火しません。matcher では大文字と小文字が区別されます。matcher が空の場合は、すべての発生時に発火します。MCP (model context protocol) server の tool は mcp__<server>__<tool> という名前になるため、matcher に "mcp__github__.*" を指定すると、1 つの server の tool だけを対象にできます。

Stop hook には、作成前に知っておくべき注意点があります。ブロックする Stop hook は model に処理を再実行させます。Claude Code は 8 回連続でブロックされると、その hook を無効化します。hook input の stop_hook_active field を読み取り、その値が true の場合は exit 0 を返してください。そうしないと、上限に達するまで hook がループします。

標準入力でフックが受け取る内容

Claude が npm test を実行しようとすると、Bash 上の PreToolUse フックは標準入力から次の内容を読み取ります。

{
  "session_id": "abc123",
  "cwd": "/home/deploy/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

すべてのイベントには session_id、cwd、permission_mode、transcript_path、hook_event_name が含まれます。ツールイベントには tool_name、tool_input、tool_use_id も追加されます。その他のイベントには固有のフィールドがあります。UserPromptSubmit には prompt のテキストが入り、SessionStart には startup の source、resume、clear、compact、fork のいずれかが入ります。

シェルスクリプト内でこれを読み取る通常の方法は jq です。最小構成のサーバーイメージには含まれていないため、Ubuntu と Debian では sudo apt install -y jq を使って先にインストールします。

ツール呼び出し中の処理に終了ステータスが与える影響

結果は 3 通りです。

  • Exit 0 は、フックが異議を唱えていないことを示します。PreToolUse では、これは承認と同じではなく、通常の権限確認フローが引き続き実行されます。UserPromptSubmit と SessionStart では、stdout がモデルのコンテキストに追加されます。
  • Exit 2 は、PreToolUse など、ブロック可能なイベントでアクションをブロックします。また、stderr がモデルに表示される理由になります。PostToolUse のようにブロックできないイベントでは、ブロックは無視されます。ただし、stderr はフィードバックとしてモデルに渡されます。
  • その他の終了コード は、ブロックを伴わないエラーです。アクションは続行されます。トランスクリプトには、Failed with non-blocking status code: に続いて stderr の 1 行目を含むフックエラー通知が表示されます。

ブロックまたは何も返さない場合以外は、Exit 0 を返し、stdout に JSON オブジェクトを出力します。PreToolUse フックでは、permissionDecision によって判断します。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database drops go through a migration, not through the agent."
  }
}

"allow" は対話プロンプトを省略し、"deny" は呼び出しをキャンセルして理由をモデルに送信し、"ask" は通常どおりプロンプトを表示します。フックごとにどちらかの方式を選択してください。Exit 2 と stdout の JSON による判断を混在させると、結果の確認が必要になります。

1 つのイベントに複数のフックが一致すると、それらは並列に実行され、すべて完了まで動作します。あるフックの deny は、他のフックを停止しません。そのため、ガードレールフックが同じ呼び出しを拒否しても、ロギングフックはその行を記録できます。Claude Code はその後、各応答を統合し、deny、defer、ask、allow の順に最も制限の強い結果を採用します。

例1: 実行前に破壊的なコマンドをブロックする

これをプロジェクト内の .claude/hooks/block-destructive.sh として保存します。

#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
  if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
    echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
    exit 2
  fi
done

exit 0

実行可能にしてから、.claude/settings.json 内の PreToolUse に登録します。

chmod +x .claude/hooks/block-destructive.sh
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
            "timeout": 10,
            "statusMessage": "Checking the command against policy"
          }
        ]
      }
    ]
  }
}

信頼する前に、スクリプトを手動でテストしてください。フックが自身の入力でクラッシュすると、許可側に倒れるためです。

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
  | .claude/hooks/block-destructive.sh
echo $?

stderr に Blocked by policy: の行が表示され、終了コードが 2 になることを確認します。ls -la のような無害なコマンドを与えた場合は、出力がなく、終了コードが 0 になるはずです。セッションでは、拒否された呼び出しがメッセージを理由としてトランスクリプトに表示され、モデルはそのメッセージを読み取って動作を調整します。

これを行う価値があるのは、PreToolUse フックがすべての権限モードで、権限モードのチェックより前に実行されるためです。そのため、bypassPermissions の場合でも拒否が有効になります。これにより、Claude Code の自動モードと権限設定 とフックを併用できます。プロンプトによる確認を減らしても、フックは実行されます。

ただし、これで実現できる範囲を正しく理解してください。コマンド文字列のパターンマッチングは、エージェントの不注意に対するガードレールです。エージェントが巧妙に回避する場合の境界にはなりません。同じコマンドでも、grep が認識できない形式で記述できるためです。強制すべきルールは、権限システムと、プロセスを実行するアカウントに設定してください。

例2: 編集するたびにフォーマットと lint を実行する

PostToolUse と Edit|Write matcher を組み合わせると、ファイルを編集するすべてのツールの後に実行できます。これを .claude/hooks/after-edit.sh として保存します。

#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0

case "$FILE" in
  *.py)
    ruff format "$FILE" >/dev/null 2>&1
    if ! ruff check "$FILE" >&2; then
      exit 2
    fi
    ;;
  *.sh)
    if ! shellcheck "$FILE" >&2; then
      exit 2
    fi
    ;;
esac

exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Claude に Python ファイルへインデントが崩れた関数を追加するよう依頼し、その後でファイルを開きます。ファイルはフォーマット済みの状態で表示されます。これで hook が実行されたことを確認できます。hook が成功すると、会話には何も表示されないためです。

ここでの exit 2 は、変更を取り消しません。PostToolUse はツールの実行後に発火するため、どちらの場合でも編集内容はディスクに保存されます。exit 2 によって得られるのは、ruff check の出力がフィードバックとして model に渡されることです。そのため、Claude は直前に導入したエラーを修正してから処理を続けます。これは、commit 時に見つかる lint 失敗と、agent が同じ turn で修正する lint 失敗の違いです。

ここでは、matcher に関する2つの制限が重要です。Edit|Write は shell command によって変更されたファイルを認識しません。また、Claude は十分な頻度で Bash を通じてファイルを書き込むため、この欠落は実際の問題になります。呼び出しごとに確実に適用するには、Bash も照合し、スクリプトで変更されたファイルを git status --porcelain により一覧表示します。turn ごとに1回だけ適用するには、代わりにスキャンを Stop hook に配置します。

例 3: 監査用にすべてのツール呼び出しを記録する

PostToolUse の空の matcher は、すべてのツールで実行されます。記録をホームディレクトリ内のファイルではなく system journal に送ることで、agent 自身の shell からアクセスできない場所に保管できます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
          }
        ]
      }
    ]
  }
}

journalctl -t claude-code -o cat | tail -n 5 でログを読み返します。ツール呼び出しごとに 1 行の JSON が出力され、最後の行が最新になっているはずです。何も表示されない場合は hook が実行されていません。下記のトラブルシューティングでは、この問題を扱います。

失敗した呼び出しも記録するには、PostToolUseFailure の下に同じブロックを追加します。PostToolUse は成功時にしか実行されず、失敗したコマンドのほうが通常は重要だからです。ホームディレクトリ内のファイルへの追記ではなく logger を使う理由は、所有権にあります。hook は agent の shell と同じユーザーとして実行されるため、そのユーザーが追記できるものは、そのユーザーが切り詰めることもできます。journal は systemd-journald が専用アカウントで書き込みます。

フックの実行時間

ChartDefault hook timeout in seconds, by hook type and event
The data behind this chart
[
  {
    "label": "command, http or mcp_tool hook",
    "default_timeout_seconds": 600
  },
  {
    "label": "agent hook",
    "default_timeout_seconds": 60
  },
  {
    "label": "prompt hook",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on UserPromptSubmit",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on MessageDisplay",
    "default_timeout_seconds": 10
  },
  {
    "label": "any hook on SessionEnd",
    "default_timeout_seconds": 1.5
  }
]

コマンドフックのデフォルト実行時間は 600 秒で、10 分です。ただし、一部のイベントではこの時間が大幅に短くなります。SessionEnd フックはすべて合わせて 1.5 秒の予算を共有するため、セッション終了時のクリーンアップは迅速に行う必要があります。ただし、フックでより長い timeout を設定すると、共有予算も同じ値まで延長されます。上限は 60 秒です。

タイムアウトに達したフックはキャンセルされ、判定を返しません。PreToolUse ガードレールの場合、これは処理をブロックしないことを意味します。ツール呼び出しは通常の権限フローへ進みます。そのため、ガードレールのスクリプトは小さく保ってください。ログの別の場所への送信など、処理の完了を待つ必要がない時間のかかる処理では、"async": true を設定します。フックはバックグラウンドで実行され、ツール呼び出しを待たせません。

Hooks、rules file、skill、MCP server

これらはすべて agent の動作を変えるため、互いに混同されがちです。ただし、提案として扱われなくなるのは 1 つだけです。

rules file(CLAUDE.md、または .claude/rules/ 配下のファイル)は、model のコンテキストに読み込まれるテキストです。動作を方向付けますが、強制はしません。長い会話、大きな差分、新しい user request が重なると、その 1 行が無視されることがあります。これは、記述した指示を agent が無視する一般的な仕組みです。

skill は、model が関連性を判断したときに読み込む指示とスクリプトのフォルダーです。その判断こそが skill の目的であり、同時に限界でもあります。最終的には model が決定します。Ponytail は agent に実用上最小限の変更を促すskill の好例です。hook では実現できない方法でタスク全体への取り組み方を方向付けられますが、model が読み込むことを選択した場合に限られます。

MCP(model context protocol)server は、model が呼び出せる新しいツールを提供します。これにより agent がアクセスできる範囲が広がります。ただし、agent にそのツールを使わせるわけではありません。また、別プロセスとして運用する必要があり、それ自体が独立した作業になります。詳しくは VPS での MCP server の運用を参照してください。

4 つの中で、model の選択なしに実行されるのは hook だけです。好みを指定する場合は rules file を、適用時に model が従うべき手順を指定する場合は skill を使います。毎回必ず実行する必要がある処理や、決して実行してはならない処理には hook を使います。skill が rules file より適する場合を含む詳しい比較は、skill、MCP、rules file の比較で説明しています。

plugin は 5 つ目の仕組みではなく、パッケージ化の単位です。hook と skill を 1 つのインストール可能な単位にまとめます。これにより、チームは同じガードレールをすべてのマシンに導入できます。詳しくは Claude Code plugin の仕組みを参照してください。

共有 VPS におけるセキュリティ判断

Hook は agent が呼び出すコードで、Claude Code を起動したユーザーとして実行されます。そのユーザーの環境とファイル権限を引き継ぎます。Laptop ではワークフロー上の問題です。agent が無人で動作する VPS では、4 つの実務的な観点を持つセキュリティ上の問題です。

Repository 内の hook は、自分が作成していないコードです。 .claude/settings.json は commit されるため、repository を clone して、その中で session を開始すると、repository に含まれていた hook が登録される可能性があります。Claude Code は、その folder に対する workspace trust dialog の背後で project hook を制御します。つまり、trust を受け入れる時点で、それらを実行するかどうかを決めることになります。最初に hooks block を確認してください。

Hook は tool input 全体を参照できます。 tool_input を記録する audit hook は、すべての command のすべての引数を file に書き込みます。command line 上にたまたま存在した token も含まれます。その log には secret と同じ保護が必要です。これは、secret を AI agent の到達範囲外に保つことという、より広い問題の一部です。

Hook は model の context に書き込めます。 SessionStart または UserPromptSubmit hook が stdout に出力した内容は、conversation に追加されます。外部、issue tracker、または log file から text を pipe する hook は、あたかも自分で入力したかのように、信頼できない text を model に渡します。同じ VPS 上の別の Claude Code sessionから note を転送する hook も同じです。一方の agent の出力が issue tracker の出力より信頼できるわけではありません。その stdout は出力ではなく input として扱ってください。

実際の制御手段は privilege です。 agent は専用の unprivileged user として実行し、必要な sudo rule だけを与えてください。PreToolUse deny は設定する価値があります。ただし、これは設計上 best effort です。reference も if filter について同じ説明をしており、確実な deny が必要な場合は permission system を使用するよう案内しています。厳しい状況でも機能するのは、permission rule と process が実行される account です。

どの構成でも成り立つ性質が 1 つあります。PreToolUse hook は、すべての permission mode で permission-mode check より前に実行されます。そのため、hook が deny を返すと、bypassPermissions の場合でも tool は block されます。Hook によって、permission rule が許可する操作をさらに制限できます。緩和することはできません。

フックが実行されないのはなぜですか?

次の順序で確認してください。各手順では、実際に表示される症状を示します。

  • /hooks を実行し、想定したイベントの下にフックが表示されることを確認します。メニューにフックが表示されない場合は、通常、設定ファイルの JSON 構文エラーが原因です。末尾のコンマとコメントは使用できません。また、設定ファイルが上記の 6 か所のいずれにもない可能性があります。
  • matcher とツール名が完全に一致しているか比較します。matcher は大文字と小文字を区別するため、"bash" は Bash ツールに一致しません。
  • 上記の例 1 と同様に、サンプル入力を使ってスクリプトを手動で実行します。想定外の終了コードが返る場合は、スクリプトの不具合です。Claude Code はこれを判定結果ではなく、フックエラーとして報告します。
  • jq: command not found という通知が表示される場合、そのマシンに jq がありません。自分のスクリプトに対する command not found は、パスを解決できなかったことを示します。その場合は ${CLAUDE_PROJECT_DIR} または絶対パスを使用します。スクリプトがまったく実行されない場合は、実行可能属性が付いていない可能性があります。
  • フックが有効な JSON を出力しているのに何も起きない場合があります。シェル形式のフックは sh -c 経由で実行されます。シェルのプロファイルがバナーを出力すると、そのバナーが JSON の前に追加されます。標準出力が { で始まらなくなるため、Claude Code は全体をプレーンテキストとして読み取り、判定結果を無視します。終了コードが 0 の場合、デバッグログ以外には何も報告されません。プロファイル内の echo は、インタラクティブシェルでのみ実行されるように囲んでください。
  • それでも解決しない場合は、claude --debug-file /tmp/claude.log を指定してセッションを開始し、2 つ目のターミナルで tail -f /tmp/claude.log を実行します。デバッグログには、一致したフック、各フックの終了コード、標準出力と標準エラー出力に書き込まれたすべての内容が記録されます。

FAQ

Claude Code の hook と CLAUDE.md の指示にはどのような違いがありますか?

CLAUDE.md の指示はモデルのコンテキスト内のテキストです。そのため、会話や現在の要求と注意を競合し、モデルはそれらと比較して重み付けできます。hook は Claude Code がライフサイクルの固定された時点で実行する shell command です。そのため、モデルの判断にかかわらず、イベントが発生するたびに実行されます。好みを指定する場合は指示を使います。必ず実行する処理や、決して実行してはならない操作には hook を使います。

Claude Code が特定の shell command を実行しないようにするにはどうすればよいですか?

PreToolUse hook を、.tool_input.command から command を読み取り、理由を stderr に書き出して 2 で終了する Bash matcher とともに登録します。Claude Code は呼び出しをキャンセルし、その理由をモデルに表示します。これは permission-mode の確認より前に行われるため、bypassPermissions mode でも拒否が有効です。command 文字列のパターンマッチングは、パターンを回避する形式で同じ command を記述できるため、セキュリティ境界ではなくガードレールです。permission rules と権限のないアカウントも併用してください。

hook が有効な JSON を出力するのに何も起きません。なぜですか?

最も多い原因は shell profile です。args フィールドのない hook は sh -c 経由で実行されます。一部の profile はすべての shell で banner を出力するため、JSON より前に stdout へ出力されます。出力が { で始まらなくなると、Claude Code は全体を plain text として扱い、判定を無視します。さらに、終了コードが 0 の場合は transcript に何も報告されません。profile 内の echo を interactive shell のテストで囲み、claude --debug-file /tmp/claude.log から debug log を読んで修正を確認してください。

共有サーバーで Claude Code hooks を実行しても安全ですか?

hook は Claude Code を起動したユーザーとして、そのユーザーのファイル権限で実行されます。そのため、そのアカウントが実行できる操作は hook も実行できます。リスクの大半は、次の 2 つの習慣で抑えられます。agent は専用の権限のないアカウントとして実行し、sudo policy を限定します。また、workspace trust dialog を承認する前に、リポジトリの hooks block を確認してください。project hooks は .claude/settings.json 内に含まれているためです。どの hook も実行させない場合は、設定ファイルで "disableAllHooks": true を設定します。