Claude Code PostToolUse フックが動かない:ログ確認とデバッグの手順

※本記事にはプロモーション(広告・アフィリエイトリンク)を含みます。
PostToolUse フックにスクリプトを設定したのに、どうやら動いていない——そういう状況で最初に困るのが「どこを見ればエラーが分かるか」という点です。Claude Code のフックは失敗しても Claude の返答には影響が出ないため、無音で落ちていることがあります。この記事では設定の確認から自前ログの仕込み方、よくある失敗パターンと対処まで、順を追って説明します。
PostToolUse フックの設定と動作の概要
Claude Code の hooks は .claude/settings.json(プロジェクト単位)または ~/.claude/settings.json(グローバル)に記述します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "/path/to/your-script.sh"
}
]
}
]
}
}
PostToolUse は Claude がツールの実行を終えた直後に呼ばれます。スクリプトには標準入力(stdin)経由で JSON が渡され、tool_name・tool_input・tool_response などのフィールドが含まれています(公式ドキュメント「Hooks」より)。
出力の扱いは次のとおりです。
| 出力先 | Claude 側での扱い |
|---|---|
| stdout | 画面には表示されない。詳細な扱いはバージョンによって異なる場合があります(公式ドキュメントの Hooks セクションを参照) |
| stderr | Claude の UI 上に警告として表示される |
| 終了コード 0 | 正常終了 |
| 終了コード 非0 | フックエラーとして記録される |
stdout が表示されないことを知らないと、「echo で確認しているのに何も出ない」という状況になります。
まずスクリプトが呼ばれているかを確認する
原因を絞る前に、「そもそもスクリプトが起動しているか」を確認します。以下のようなログ専用の最小スクリプトを一時的に設定してください。
#!/bin/bash
# /tmp/hook-debug.log に実行の痕跡を残す
echo "$(date '+%Y-%m-%d %H:%M:%S') hook called" >> /tmp/hook-debug.log
cat >> /tmp/hook-debug.log # stdin も捨てずに記録
スクリプトに実行権限を付与し、settings.json の command に絶対パスで指定します。
chmod +x /path/to/debug-hook.sh
Claude Code でファイルを編集したあとに /tmp/hook-debug.log が作られなければ、スクリプトが一切呼ばれていません。以下を順に確認します。
- JSON の構文エラー —
jq . ~/.claude/settings.jsonを実行してエラーが出ないか確認します。 - matcher の不一致 —
matcherは正規表現で、Claude が使うツール名(Write・Edit・Bashなど)と照合されます。大文字小文字も含めて正確に書く必要があります。使ったツール名が不明なら"matcher": ".*"で全マッチにして確認します。 - 実行権限の不足 —
ls -l /path/to/your-script.shでxビットが付いているか確認します。
よくある失敗パターンと対処法
ログが出ても処理が途中で止まる場合は、以下のパターンを疑います。
PATH が通っていない
Claude Code が起動している環境の PATH は、通常のターミナルと異なることがあります。スクリプト内で python3・node・jq などをコマンド名だけで呼ぶと「コマンドが見つからない」エラーになります。
#!/bin/bash
# スクリプト冒頭で PATH を明示する
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
which python3 などで事前に確認したフルパスをそのまま使う方法が確実です。
stdin を読まずに終了している
フックに渡された JSON は stdin から届きます。スクリプトが stdin を読まないと、パイプが詰まって予期しない挙動が起きることがあります。stdin は必ず読み取るか捨ててください。
Bash の場合:
INPUT=$(cat) # stdin を変数に取る
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
echo "$(date) tool=$TOOL_NAME" >> /tmp/hook-debug.log
Python の場合:
#!/usr/bin/env python3
import sys, json, datetime
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError:
payload = {}
tool_name = payload.get("tool_name", "unknown")
with open("/tmp/hook-debug.log", "a") as f:
f.write(f"{datetime.datetime.now()} tool={tool_name}\n")
終了コードが意図せず非0になる
set -e を使っているスクリプトでは、途中のどこかのコマンドが失敗すると即座に非0で終了します。デバッグ中は set -e をコメントアウトし、エラーになりうる行に || true を付けて最後まで実行させ、原因箇所を特定します。
#!/bin/bash
# set -e ← デバッグ中はコメントアウト
INPUT=$(cat)
jq -r '.tool_name' <<< "$INPUT" || true
echo "処理完了" >> /tmp/hook-debug.log
処理が重くてタイムアウトする
API コールやファイルの大量処理をフックの中で同期実行すると、Claude Code 側のタイムアウトに引っかかることがあります。具体的なタイムアウト値はバージョンによって変わる可能性があるため、公式ドキュメントの Hooks セクションを確認してください。
重い処理はバックグラウンドに逃がし、フック自体は素早く終わらせます。
#!/bin/bash
INPUT=$(cat)
# バックグラウンドで非同期実行
/path/to/heavy-script.sh <<< "$INPUT" &
exit 0
症状別チェックリスト
| 症状 | 最初に確認すること | 対処 |
|---|---|---|
| ログファイルが作られない | JSON構文・matcher・実行権限 | jq lint・chmod +x・ツール名確認 |
| ログが途中で止まる | スクリプト内のエラー | set -e を外す・|| true でラップ |
| UI に警告が表示される | stderr の出力 | メッセージを読んで対処 |
| 処理が返ってこない | stdin 読み残し・タイムアウト | cat で stdin を読む・バックグラウンド化 |
| コマンドが見つからない | PATH の違い | フルパス指定・PATH を明示 |
まとめ
PostToolUse フックのデバッグは、まず /tmp/hook-debug.log に一行書くだけの最小スクリプトで「呼ばれているか」を確認するところから始めます。呼ばれていなければ settings.json の JSON 構文・matcher・実行権限を順に確認します。呼ばれているが途中で止まるなら、stdin の読み取り・PATH・終了コードを疑います。
次の一手は、デバッグ用の最小スクリプトを settings.json に設定して、実際に Claude Code でファイルを編集し、/tmp/hook-debug.log の有無を確認することです。ここが通れば、あとは本来の処理(フォーマッタの起動・Slack 通知・差分ログの記録など)を組み込んでいくだけです。
この記事で触れたもの
- シェルスクリプト 入門書楽天で探す
よくある質問
PostToolUse の matcher にすべてのツールを対象にする書き方はありますか?
公式ドキュメントによると matcher は正規表現です。「.*」を指定するとすべてのツール名に一致します。動作確認が終わったら「Write|Edit」のように対象を絞ることを推奨します。
フックスクリプトのエラーはどこで確認できますか?
スクリプト内で `echo 'メッセージ' >&2` と stderr に書くと Claude Code の UI 上に警告として表示されます。また /tmp/hook-debug.log など自前ファイルに書き込む方法が環境を問わず最も確実です。
PostToolUse フックで終了コードが非0になると Claude の処理は止まりますか?
公式ドキュメントによると非0終了はエラーとして記録されます。Claude の処理継続かブロックかはバージョンや設定によって異なる可能性があるため、実際の挙動は手元で確認することを推奨します。
settings.json はプロジェクト用とグローバル用のどちらに書けばよいですか?
特定プロジェクトにだけ適用したい場合は .claude/settings.json(プロジェクトルート直下)、すべてのプロジェクトで使いたい場合は ~/.claude/settings.json に記述します。両方ある場合はマージされます。