Claude Media
Claude Code Hooksの設定方法 — 初めて書く人の完全手順

Claude Code Hooksの設定方法 — 初めて書く人の完全手順

Claude CodeのHooksはツール実行の前後に任意コマンドを挟める仕組み。初めて書く人が動くところまで到達できるよう、最小例→実用例の順に手順を追います。

要点

Hooksは .claude/settings.json に10行ほど書くだけで設定できます。つまずきやすいのは、イベントの選び方と、hookへの入力の受け取り方の2点です。

ここでは PostToolUse でWrite後に自動でフォーマッタを走らせる実用例を、最小構成から順に手を動かして作ります。

hookに渡される情報は、環境変数ではなく標準入力のJSONです。この1点を押さえておくと、ログ出力もフォーマッタ連携も同じ形で書けます。

前提条件

  • Claude Codeがインストール済み(claude --version が通る)
  • Node.jsプロジェクトでPrettierが導入済み(npx prettier --version が通る)
  • jq が使える(jq --version が通る)
  • macOS / Linux / WSL(Windows PowerShellの書き方は「うまくいかないとき」で触れます)

手順1: 設定ファイルを作る

プロジェクトルートに .claude/ ディレクトリと settings.json を作ります。

mkdir -p .claude
touch .claude/settings.json

ls -la .claude/ で settings.json が見えれば準備完了です。

手順2: 最小のHookを書く

.claude/settings.json に以下を書きます。まずはツール名と対象ファイルをログに書き出すだけのhookで、設定が効いていることと、hookが受け取る入力の形を確かめます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '\"[hook] \\(.tool_name) \\(.tool_input.file_path)\"' >> /tmp/claude-hooks.log"
          }
        ]
      }
    ]
  }
}
くらべる

hookが入力を受け取る場所

こちらが正

標準入力(stdin)

イベントごとのJSONが標準入力に流れてきます。jq で .tool_name や .tool_input.file_path を取り出します。hook_event_name や cwd などの共通項目も同じJSONに入っています。

入力は入っていない

環境変数

$CLAUDE_HOOK_INPUT のような名前の環境変数は、hookの入力としては使えません。参照すると空文字になり、下のとおりコマンドは何も実行しないまま成功扱いで終わります。

標準入力で受け取る形は、手元のシェルで同じJSONを jq に流せば再現できます。環境変数を参照するコマンドは、変数が未設定のため何も出力せず、終了コード0で終わりました。

J='{"hook_event_name":"PostToolUse","tool_name":"Write","tool_input":{"file_path":"/tmp/hk/src/utils.ts","content":"x"}}'
 
# 標準入力で受け取る
echo "$J" | jq -r '"[hook] \(.tool_name) \(.tool_input.file_path)"'
# -> [hook] Write /tmp/hk/src/utils.ts
 
# 環境変数で受け取ろうとする(変数は未設定)
bash -c 'FILE=$(jq -r ".tool_input.file_path // empty" <<< "$CLAUDE_HOOK_INPUT") && [ -n "$FILE" ] && echo "RUN $FILE" || true'
# -> (何も出ない。終了コードは0)

設定の各項目の意味は次のとおりです。

  • PostToolUse は「ツール実行後」に走るイベントで、ツールが成功したときに発火します。失敗時は別の PostToolUseFailure
  • matcher: "Write" でWriteツールだけに限定します。*、空文字、省略のいずれかなら全ツールが対象
  • command はただのシェルコマンドです。終了コード0のときのstderrはデバッグログにだけ残り、会話には出ません

この記事で使うのは command タイプです。hookの種別にはほかに http、prompt、agent、mcp_tool があります。

手順3: 反映を確かめる

settings.jsonを直接編集した場合、hookの変更は通常ファイル監視によって実行中のセッションに自動で反映されます。Claude Codeを再起動しなくても動くのが通常の挙動です。

まずClaude Codeの入力欄で /hooks を開き、PostToolUse の下に先ほどのhookが並んでいるかを見ます。一覧に出ていれば、設定ファイルはJSONとして読めています。

次に、Writeを含むタスクを頼みます。

README.md に "Hello Hooks" と書いて保存してください

別のターミナルでログを見ます。

cat /tmp/claude-hooks.log
# -> [hook] Write /path/to/project/README.md

ログが書かれていれば成功です。/hooks に出てこない、またはログが増えないときは、数秒待っても反映されない場合にセッションを再起動します。ファイル監視が変更を取りこぼすことがあり、再起動すれば確実に読み直されます。

手順4: Prettierを走らせる実用版に拡張

ログだけのhookを、Prettierを走らせるhookに置き換えます。

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

仕組みは3段です。

  • matcher: "Edit|Write" は、| 区切りの完全一致の指定です。英数字・_・-・空白・,・| だけで書いた値は、正規表現ではなく名前の完全一致として扱われます
  • jq -r '.tool_input.file_path' が、標準入力のJSONから編集されたファイルのパスを取り出します
  • xargs npx prettier --write が、そのパスをPrettierに渡して書き換えます

matcherに . や ^ のような記号を含めると、その値は正規表現として扱われ、文字列のどこかに一致すれば発火します。たとえば Edit.* は Edit だけでなく NotebookEdit にも当たります。ツール名を厳密に絞りたいときは、記号を使わず Edit|Write のように名前を並べるのが簡単です。

特定の拡張子に限りたいときは、hookに if フィールドを足し、"Edit(*.ts)" のようにツール名とパスを一緒に書く方法があります。if はツールイベント(PreToolUse や PostToolUse など)でだけ評価され、権限ルールと同じ書式です。

手順5: 動作確認

任意のJavaScript / TypeScriptファイルに、シングルクォートの文字列を書かせてみます。

src/utils.ts を作って、シングルクォートで文字列を返す foo 関数を書いてください

作成されたファイルを開きます。

cat src/utils.ts

Prettierの既定設定なら、シングルクォートがダブルクォートに書き換わっているはずです。hookが成功したときは会話には何も表示されないので、整形された結果ファイルが唯一の証拠になります。

hookの動きを見たいときは、次の2つが使えます。

  • Ctrl+O でトランスクリプトを開くと、ブロックされた場合のフィードバックや hook error の通知が見えます。成功した実行は何も表示されません
  • claude --debug-file /tmp/claude.log で起動して別ターミナルから tail -f /tmp/claude.log を実行すると、どのhookが一致したか、終了コード、stdout、stderrまで読めます。起動後なら /debug で途中から有効にできます

claude --help にも --debug-file <path> の説明があり、ファイルを指定するとデバッグモードが暗黙に有効になります。

終了コードで挙動が変わる

手順2と手順4のhookは、どちらも常に終了コード0で終わります。PostToolUse ではツールが既に実行済みなので、hookに「やめさせる」力はなく、使い分けは次の2つです。

くらべる

PostToolUseのhookが返せる結果

静かに成功

終了コード0

何も起きません。stderrに書いた内容はデバッグログにだけ残り、Claudeにも会話にも届きません。

Claudeにフィードバック

終了コード2

stderrの内容がClaudeに渡ります。ツールは既に実行済みで取り消せませんが、Claudeは内容を読んで次の行動を変えられます。

たとえばリンターの警告をClaudeに読ませたいなら、警告をstderrに出して exit 2 で終わるシェルスクリプトにします。整形ではなく「指摘して直させる」使い方になります。

注意点は、exit 1 では止まらないことです。ほとんどのイベントで、コードだけで処理を止められる終了コードは2だけで、1は非ブロッキングのエラーとして扱われ、処理は先に進みます。PreToolUse で危険なコマンドを止めたいときは、必ず exit 2 を使います。

うまくいかないとき

症状から原因をたどる順番は、次のとおりです。

手順

hookが動かないときの切り分け順

  1. 1

    /hooks に出ているか

    一覧に出ないときは、JSONの不備(末尾のカンマや引用符の閉じ忘れ)か、ファイルの置き場所の違いを疑います。数秒待っても出ないときだけ再起動します。

    一覧が空で Only hooks from managed settings run here と表示される場合は、組織の管理設定で allowManagedHooksOnly が有効になっています。自分で書いたhookは動かないので、管理者に確認します。

  2. 2

    matcherが対象のツール名に合っているか

    ツール名との比較は完全一致です。完全一致なので、綴りが違うと発火しません。

  3. 3

    Ctrl+O で hook error が出ていないか

    hook error の通知には、stderrの1行目が付きます。

  4. 4

    デバッグログで実行内容を読む

    --debug-file で出力した内容に、一致したhook、終了コード、stdoutとstderrが残っています。

症状: jq: command not found

原因は jq が未インストールなことです。brew install jq(macOS)や apt install jq(Debian系)で入れます。node のスクリプトに置き換えても同じことができ、標準入力のJSONを読むという点は変わりません。

症状: Prettierが走らない

package.json にprettierが無い、またはプロジェクトルート以外で動いていることが原因として考えられます。npm install --save-dev prettier で入れたうえで、hookの入力JSONにある cwd がプロジェクトルートかを見ます。手順2のログhookで .cwd も書き出せば確かめられます。

症状: Windows PowerShellで動かない

例のコマンドはPOSIXシェル前提です。hookには shell フィールドがあり、"shell": "powershell" を指定すればWindowsでPowerShellを使ってコマンドを実行します。CLAUDE_CODE_USE_POWERSHELL_TOOL の設定は不要で、hookはPowerShellを直接起動します。shell を省略すると既定は bash で、Git Bashが入っていないWindowsでは既定がPowerShellになります。

症状: hookが遅くてClaudeの応答が詰まる

command タイプのhookの既定タイムアウトは600秒で、ほとんどのイベントでは終わるまでClaudeが待ちます。重い処理は、次の2つで軽くできます。

  • timeout に秒数を指定して、上限を短くする
  • "async": true を指定して、バックグラウンドで実行する

async: true のhookには timeout が強制されません。時間のかかる処理を非同期にしたときは、処理自体に終了条件を持たせます。

UserPromptSubmit のhookは例外で、既定タイムアウトが30秒に短縮されています。毎回のプロンプトの前に走って応答を止めるため、止まったhookがセッション全体を巻き込みます。

次のイベントへ進む

ここまで使った PostToolUse は、イベント全体のごく一部です。用途ごとに選ぶ目安は次のとおりです。

やりたいこと使うイベント補足
危険なコマンドを事前にブロック使うイベントPreToolUse補足exit 2 かJSONの permissionDecision: "deny"
入力待ちを通知使うイベントNotification補足Claudeが入力や許可を待つとき
応答完了後に処理使うイベントStop補足Claudeが応答を終えたとき
セッション開始時に文脈を足す使うイベントSessionStart補足起動・再開・クリア時など
設定ファイルの変更を監視使うイベントConfigChange補足変更をブロックもできる

セッション終了そのものには SessionEnd が別にあり、Stop は「Claudeが1回の応答を終えたとき」に発火します。区別は、コミット支援のような処理で効いてきます。

Hooksは実行時の自動化レイヤーです。プロジェクトの暗黙のルールはCLAUDE.mdに、繰り返し呼び出したい知識はSkillsに置くと、役割が重なりません。

まとめ

hookの入力は標準入力のJSONで、環境変数ではありません。ここを間違えると、コマンドは何もせず成功扱いで終わり、気づきにくい不具合になります。

最小のログ版で /hooks とログを確かめてからPrettier版に進めれば、問題の切り分けが短く済みます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.claude.com/ja/docs/claude-code/hooks
  2. code.claude.com/docs/ja/hooks
  3. code.claude.com/docs/en/hooks
  4. code.claude.com/docs/en/hooks-guide
この記事を共有:XはてブLinkedIn