Claude Code の hooks が受け取る stdin JSON の構造:共通項目とイベント別の項目

Claude Code 公開:

hooks のコマンドは標準入力で JSON を受け取ります。全イベント共通の項目と、PreToolUse・Stop・SessionStart などイベント別の項目を一覧にし、jq で値を取り出す実例を示します。

検証日 2026年9月17日 仕様変更が早い分野です。最新の公式ドキュメントも併せてご確認ください。
目次
  1. 全イベント共通の項目
  2. イベント別に追加される項目
    1. PreToolUse / PostToolUse
    2. その他のイベント
  3. jq で値を取り出す
  4. JSON に入らない情報は環境変数から取る
  5. まとめ

hooks でスクリプトを実行する設定は書けたものの、そのスクリプトの中で「どのファイルが編集されたのか」「どのコマンドが実行されようとしているのか」をどう知るのかが分かりません。

答えは 標準入力に流れてくる JSON です。コマンド型のフックは stdin から JSON を受け取り、HTTP 型のフックは同じ JSON を Content-Type: application/json の POST ボディとして受け取ります。この記事では、その JSON の構造を共通項目とイベント別項目に分けて整理します。

KEY POINT

この記事で分かること

  • 全イベント共通で入る項目と、それぞれの意味
  • PreToolUse・Stop・SessionStart などイベント別に追加される項目
  • jq で値を取り出す実例と、JSON にない情報の取得方法

全イベント共通の項目

どのイベントでも、次の項目がイベント固有の項目に加えて入ります。

{
  "session_id": "abc123",
  "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "effort": {
    "level": "medium"
  }
}
項目内容
session_id現在のセッションの識別子
prompt_idユーザープロンプトを識別する UUID(最初の入力までは入らない。v2.1.196 以降)
transcript_path会話 JSON のパス(現在のターンに追いついていない場合がある)
cwdフック実行時のカレントディレクトリ
scratchpad_dirセッションのスクラッチパッドのパス(利用できない場合は入らない。v2.1.257 以降)
permission_modedefault / plan / acceptEdits / auto / dontAsk / bypassPermissions
effortlevel を持つオブジェクト。値は low / medium / high / xhigh / max
hook_event_name発火したイベント名
agent_idサブエージェントの識別子(サブエージェントのコンテキストのみ)
agent_typeExplore のようなエージェント名(サブエージェント時、または --agent 使用時)

hook_event_name が入るため、1 つのスクリプトを複数のイベントで共用して、中で分岐する書き方ができます。

transcript_path は最新とは限らない

公式ドキュメントには、transcript_path が現在のターンに追いついていない場合がある(may lag current turn)と明記されています。進行中のやりとりを前提にした処理には使わないでください。

イベント別に追加される項目

PreToolUse / PostToolUse

もっともよく使うイベントです。PreToolUse には次の 3 項目が追加されます。

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite",
    "timeout": 120000,
    "run_in_background": false
  },
  "tool_use_id": "toolu_01ABC123..."
}

tool_input の中身はツールごとに異なります。Bash なら commandEditWrite なら file_path が入ります。

PostToolUsePreToolUse と同じ構造に tool_output が加わります。

{
  "tool_output": {
    "text": "Test results...",
    "error": null
  }
}

PostToolUseFailure も同じ形で、error にエラーの種類(例: TimeoutError)が入ります。

その他のイベント

イベント追加される項目値の例
UserPromptSubmituser_inputプロンプトの本文
Stoplast_assistant_messagestop_reasonend_turn
SubagentStopagent_typeagent_idlast_assistant_messagestop_reasoncode-reviewer
SessionStarthowmodelstartup / resume / clear / compact / fork
SessionEndwhymodelclear / resume / logout / prompt_input_exit / other
PreCompact / PostCompactwhatmanual / auto
Notificationtypemetadatapermission_prompt / idle_prompt / auth_success

SessionStart の項目名は howSessionEndwhyPreCompactwhat と、イベントごとに名前が違います。ここは推測せず一覧で確認してください。なお model は「常に入るわけではない」と公式ドキュメントに書かれています。

これらの値は matcher でも絞り込めます。どのイベントで何が照合されるかは hooks の matcher に書ける値一覧 にまとめています。

jq で値を取り出す

もっとも短い書き方は、コマンドに直接 jq をつなぐ形です。公式ドキュメントも、編集後に Prettier をかける例をこの形で示しています。

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

スクリプトファイルにする場合は、まず cat で全体を受け取ってから取り出します。公式ドキュメントの保護ファイル用スクリプトは次の形です。

#!/usr/bin/env bash
# protect-files.sh

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Windows のバックスラッシュ区切りを正規化して、下のパターンに一致させる
FILE_PATH="${FILE_PATH//\\//}"

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

ポイントは 2 つあります。

  1. // empty を付ける。file_path を持たないツールで発火した場合に、jqnull という文字列を返すのを防げます
  2. stdin は 1 回しか読めない。INPUT=$(cat) で変数に入れてから、必要な項目を何度でも取り出します

終了コード 2 は処理をブロックします。意味の違いは hooks の終了コードの意味と使い分け を参照してください。

用語解説

stdin(標準入力): プロセスに外部からデータを流し込む経路です。cat で受け取るとファイルのように扱えます。一度読み切ると再読み込みできないため、変数に保持するのが定石です。

JSON に入らない情報は環境変数から取る

パスの起点など、JSON に含まれない情報は環境変数で受け取ります。フックのプロセスは親の環境を継承したうえで、次の変数が追加されます。

環境変数内容
$CLAUDE_PROJECT_DIRセッションを開始したプロジェクトのルート
$CLAUDE_PLUGIN_ROOTプラグインのインストール先(プラグインのフック用)
$CLAUDE_PLUGIN_DATAプラグインの永続データ用ディレクトリ
$CLAUDE_EFFORT現在の effort(low / medium / high / xhigh / max)
$CLAUDE_CODE_REMOTEリモートの Web 環境では true
$CLAUDE_CODE_BRIDGE_SESSION_IDRemote Control のセッション ID(v2.1.199 以降、接続中のみ)

JSON の cwd は「フック実行時のカレントディレクトリ」なので、サブディレクトリで作業していると変わります。スクリプトの場所を固定して参照したいときは $CLAUDE_PROJECT_DIR を使ってください。

{
  "type": "command",
  "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
}

具体的な lint・format の組み方は hooks で lint と format を自動実行する にまとめています。

まとめ

  • コマンド型のフックは stdin から JSON を受け取る。HTTP 型は同じ JSON を POST ボディで受け取る
  • 共通項目は session_idtranscript_pathcwdpermission_modehook_event_nameeffort など
  • ツールのイベントでは tool_nametool_inputtool_use_id が入り、PostToolUse では tool_output が加わる
  • 項目名はイベントごとに違う(SessionStarthowSessionEndwhyPreCompactwhat)
  • スクリプトでは INPUT=$(cat) で保持し、jq -r '... // empty' で取り出す
  • JSON にないプロジェクトルートは $CLAUDE_PROJECT_DIR から取得する

よくある質問

hooks のスクリプトはどうやって入力を受け取りますか?
コマンド型のフックは標準入力(stdin)から JSON を受け取ります。HTTP 型のフックは同じ JSON を POST のリクエストボディとして受け取ります。
編集されたファイルのパスはどこに入っていますか?
PreToolUse と PostToolUse の `tool_input` の中です。Edit や Write なら `.tool_input.file_path` で取り出せます。
どのイベントでも使える項目はありますか?
あります。`session_id`、`transcript_path`、`cwd`、`permission_mode`、`hook_event_name` などは全イベント共通で入ります。
プロジェクトのルートディレクトリはどう取得しますか?
JSON の `cwd` はフック実行時のカレントディレクトリです。セッションを開始したプロジェクトルートは環境変数 `$CLAUDE_PROJECT_DIR` で取得します。

参考にした一次情報

この記事は公式ドキュメントを基に AI が下書きを作成し、運営者が内容を確認して公開しています。誤りを見つけた場合は お問い合わせからお知らせください。