CLAUDE LABEN
TEACHERS — AnthropicがClaude for Teachersを公開。米国K-12の認証済み教員に、プレミアム機能と授業向けスキル、全50州の学習指導基準に沿ったカリキュラム連携を無償提供しますADMIN — Claude Enterprise全組織でAdmin APIがベータ提供に。メンバーと招待の操作はベータヘッダー不要、グループとカスタムロールは専用ヘッダーが必要ですM365 — Microsoft 365コネクタに書き込みツールが追加。メールの下書き・送信・整理、カレンダー操作、OneDriveとSharePointのファイル作成・更新まで任せられますMCP — --mcp-configや.mcp.jsonのサーバ単位request_timeout_msが無視され、新規セッションで60秒既定のままタイムアウトする不具合が修正されましたSUBAGENT — --forward-subagent-textとCLAUDE_CODE_FORWARD_SUBAGENT_TEXTが追加。サブエージェントのテキストと思考をstream-json出力に含められますDEADLINE — 7月24日にOpus 4.7のfastモードが削除されます。残り7日、speed: "fast" はエラーになるためOpus 4.8への移行をご確認くださいTEACHERS — AnthropicがClaude for Teachersを公開。米国K-12の認証済み教員に、プレミアム機能と授業向けスキル、全50州の学習指導基準に沿ったカリキュラム連携を無償提供しますADMIN — Claude Enterprise全組織でAdmin APIがベータ提供に。メンバーと招待の操作はベータヘッダー不要、グループとカスタムロールは専用ヘッダーが必要ですM365 — Microsoft 365コネクタに書き込みツールが追加。メールの下書き・送信・整理、カレンダー操作、OneDriveとSharePointのファイル作成・更新まで任せられますMCP — --mcp-configや.mcp.jsonのサーバ単位request_timeout_msが無視され、新規セッションで60秒既定のままタイムアウトする不具合が修正されましたSUBAGENT — --forward-subagent-textとCLAUDE_CODE_FORWARD_SUBAGENT_TEXTが追加。サブエージェントのテキストと思考をstream-json出力に含められますDEADLINE — 7月24日にOpus 4.7のfastモードが削除されます。残り7日、speed: "fast" はエラーになるためOpus 4.8への移行をご確認ください
記事一覧/Claude Code
Claude Code/2026-04-24上級

Claude Code Hooks の本番デバッグ術 — ログが足りない時、どこから切り分けるか

ローカルでは動くのに本番で発火しない Claude Code Hooks を、どこから切り分けるか。私が実案件で使っている可観測性の仕込み方と、最小再現環境を60秒で作るテクニックを体系化しました。

hooks14claude-code129debugging5production87observability14

Claude Code の Hooks を本番導入してしばらく経つと、必ず一度は遭遇する瞬間があります。ローカルでは綺麗に動いていたスクリプトが、本番の CI ランナーや別メンバーの環境だと沈黙します。echo も出ない、ログも残らない、Claude の挙動にも影響が見えありません。原因を掴む取っ掛かりが、文字通り何もない状態です。

ここでは私が実案件で Hooks を運用する中で積み上げてきた、本番の「発火しない」を体系的に潰していく手順をまとめました。表面的な「settings.json を見直しましょう」ではなく、そもそもなぜログが取れないのか、どうすれば再現できない現象を再現できるのかという、一段下のレイヤーの話をします。

最初に疑うべきは「いつ発火したか」ではなく「どこに出力されたか」

Hooks が動かないと感じたとき、多くの人は「本当に呼ばれたのか」から調査を始めます。ですが、私の経験ではこの順序は遠回りになることが多いです。ほとんどのケースで Hooks 自体は呼ばれていて、ただ標準出力・標準エラーの行き先を取り逃しているだけ、という結論に落ち着きます。

Claude Code は Hooks のプロセスを子プロセスとして fork し、標準入力から JSON を流し込みます。標準出力は基本的に Claude 側の挙動制御に使われ、標準エラーはユーザーが目にしない場所に吸い込まれることがあります。ターミナルで実行している時は見えていた print 文が、バックグラウンドで起動された Claude Code からは見えない、というだけの話です。

ですから本番デバッグで最初に手を入れるべきは、スクリプトの内容ではなく「出力先の固定化」です。

#!/usr/bin/env bash
# hook-entry.sh — 必ず同じ場所にログを残す土台
LOG_DIR="${HOME}/.claude/hooks-debug"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/$(date +%Y%m%d).log"
 
{
  echo "---"
  echo "time: $(date -Iseconds)"
  echo "pid: $$"
  echo "hook: $CLAUDE_HOOK_EVENT"
  echo "tool: $CLAUDE_TOOL_NAME"
  echo "cwd: $(pwd)"
  echo "stdin: $(cat)"
} >> "$LOG_FILE" 2>&1

このラッパを噛ませてから、本来のスクリプトを呼ぶ設計にします。ポイントは「Hooks が発火してから、ユーザーの処理にたどり着くまでの間」に、必ず1行が残る場所を確保することです。この1行があるかないかで、「呼ばれていない」のか「呼ばれているが自分の処理が壊れている」のかが即座に分かれます。

私は過去、この切り分けをせずに 2 時間かけてスクリプト内部を調べ、最終的に「そもそも matcher が間違っていて呼ばれていなかった」というオチに行き着いたことがあります。発火自体の有無は、スクリプトの品質とは独立した問題として最初に白黒を付けるべきです。

ログ不足を補う 3 層の可観測性を組む

本番で Hooks を運用するなら、可観測性は 3 層に分けて仕込んでおくと後が楽になります。私はこれを Hooks の「可視化レイヤー」と呼んでいます。

Layer 1 — 発火の生存確認。上のラッパのように、Hooks が呼ばれた事実そのものをファイルに残す層です。スクリプトの内部に一切踏み込まない、呼ばれたか呼ばれていないかだけを記録します。

Layer 2 — 入出力の記録。標準入力で渡される JSON と、スクリプトが返そうとした標準出力・exit code をそのまま保存する層です。ここまであると「Claude 側には何が届き、こちらは何を返したのか」という通信ログが得られます。

Layer 3 — 内部処理のトレース。スクリプト内部のロジックを追うデバッグログです。ここは案件ごと、用途ごとに設計が分かれるので、Layer 1-2 をきちんと敷いた上で必要な粒度に決めます。

これを 1 枚の bash で雑にまとめると、こんな形になります。

#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${HOME}/.claude/hooks-debug"
mkdir -p "$LOG_DIR"
TRACE="$LOG_DIR/trace-$(date +%Y%m%d-%H%M%S)-$$.log"
 
# Layer 1: fire
echo "[fire] $(date -Iseconds) event=$CLAUDE_HOOK_EVENT tool=${CLAUDE_TOOL_NAME:-}" \
  >> "$LOG_DIR/fire.log"
 
# Layer 2: I/O capture
STDIN_JSON=$(cat)
echo "$STDIN_JSON" > "$TRACE.in.json"
 
# 本来のロジック
RESULT=$(echo "$STDIN_JSON" | your-actual-hook 2>> "$TRACE.err")
EXIT=$?
 
# Layer 2: response capture
echo "$RESULT" > "$TRACE.out.json"
echo "exit=$EXIT" > "$TRACE.exit"
 
# Claude に返す
echo "$RESULT"
exit $EXIT

やり過ぎに見えるかもしれませんが、これが入っているのといないのとでは、障害発生時の調査時間が桁で変わります。本番の Hooks は「動いている時は何も起きない」システムなので、動かなくなった瞬間にいきなり情報ゼロから掘り始めることになりがちです。平時の可観測性の貯金が、そのまま障害時の復旧速度になります。

発火条件を分離テストする — 最小再現環境を60秒で作る

「ローカルでは動くのに本番では動かない」系の問題で一番しんどいのは、再現手順の構築です。本番の設定は複雑で、プロジェクト固有の依存も多く、手元で同じ状況を作ろうとすると時間がかかります。私は、Hooks の再現環境はとにかく速く作ることを重視しています。具体的には、60 秒以内に「問題の Hooks だけが載った状態」を立ち上げられるようにしておきます。

ポイントは、本番プロジェクトから設定ファイルをコピーするのではなく、Hooks 1 本だけをテストする最小プロジェクトを常に持っておくことです。

mkdir -p /tmp/hooks-repro && cd /tmp/hooks-repro
cat > .claude/settings.json <<'JSON'
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "/tmp/hooks-repro/hook.sh" }
        ]
      }
    ]
  }
}
JSON
 
cat > hook.sh <<'SH'
#!/usr/bin/env bash
echo "[$(date -Iseconds)] hook fired" >> /tmp/hooks-repro/fire.log
cat > /tmp/hooks-repro/stdin-$$.json
exit 0
SH
chmod +x hook.sh

この最小構成で Claude Code を起動して、疑似的なツール実行を 1 度走らせます。ここで動けば、Hooks の仕組み自体は確実に機能している、という基準線が引けます。動かなければ、問題は設定ファイルの場所・権限・Claude Code のバージョンといったインフラ層にあると特定できます。

本番から最小構成に「引き算しながら」近づけると時間がかかりすぎるので、最小から「足し算しながら」本番に近づけていくアプローチを私はよく使います。どこで壊れるかが逆に見える、という利点があります。

JSON 入出力と exit code の挙動をプロトコル単位で把握する

Hooks が一応発火しているのに Claude の挙動に反映されない、というもう一つの典型パターンがあります。これはほとんどの場合、exit code と標準出力の使い分けを取り違えています。

Claude Code の Hooks プロトコルは、おおまかに次のルールで動きます。

  • 標準入力には JSON(イベント情報)が流れてくる
  • 標準出力に JSON を返すと、Claude 側でその内容に応じた制御が行われる
  • exit code が 0 なら「Hook は通過」、非 0 なら「Hook が何らかの判断をした」とみなされる
  • 特定の exit code(例えば Anthropic 公式が定義する「blocking」コード)は、ツール実行を中断させる意味を持つ

ここで頻繁に見る失敗は「人間が読むためのメッセージを標準出力にそのまま流してしまい、結果として JSON パースエラーになってしまう」ケースです。echo "処理を開始します" のような気軽な出力が、Claude 側から見ると「不明な JSON が返ってきた」扱いになり、本来返すべき制御情報が無視されます。

私が必ず守っているルールは次のとおりです。

  • 人間向けログは標準エラーか、専用のログファイルにだけ書く
  • 標準出力には Claude プロトコル用の JSON のみを書く
  • JSON を返すときは最後に必ず改行を入れる(行区切りを期待する実装があるため)
  • exit code は「通過=0」「ブロック=意図した非ゼロ」の 2 値に絞って設計する

このルールを Hooks スクリプトのテンプレート段階で固定しておくと、後から運用者が変わっても事故が起きにくくなります。

マルチユーザー環境での Hooks — 権限・パス・競合の診断

チームで Claude Code を使い始めると、個人ローカルでは起きなかった問題が噴出します。特に多いのが、次の 3 つです。

  1. 実行権限の欠如:Hooks スクリプトに chmod +x が付いていありません。git はパーミッションを一部しか保存しないので、環境によって挙動が変わります。
  2. パスのユーザー依存/home/alice/bin/hook.sh のような絶対パスが settings.json に書かれています。別ユーザーの環境では意味が変わります。
  3. 同時実行での競合:複数のエディタセッションが同じログファイルに書き込み、末尾が壊れる。

診断順序としては、まず「実行権限」を疑います。ls -lx が立っているか確認し、立っていなければ .gitattributes または lefthook 的な仕組みで自動付与する運用に切り替えます。次に「パス」を見ます。$HOME を使うか、プロジェクトルート相対の .claude/hooks/xxx.sh に統一するのが鉄則です。最後に「競合」は、ログファイル名に $$(PID)を含めるだけで 9 割は回避できます。

さらに CI 上で Hooks を走らせている場合、ユーザーが root 相当になっていて、$HOME/root に解決されてしまうケースもあります。本番の Hooks スクリプトで $HOME を直接使うのは、可搬性の観点では避けたいところです。プロジェクト内に自己完結させる設計の方が、長い目で見て安全です。

Hooks 本番デバッグの「再現できない」を潰す日常的な仕込み

ここまでは障害が起きてからの話をしてきましたが、本当に効くのは平時の仕込みです。Hooks の再現困難な不具合は、平時に次の 3 つを入れておくだけで大半が潰せます。

その1 — 必ず最初に来る「sentinel hook」を置く。本来やりたい処理の前に、「呼ばれた事実だけを記録する」超軽量なフックを必ず挟みます。これがあれば「呼ばれていない」のか「呼ばれたが失敗している」のかが一瞬で分かります。

その2 — バージョン情報をログに残す。Claude Code のバージョン、スクリプトの git SHA、OS・シェルのバージョンをログに残しておきます。本番でしか起きない不具合の原因は、8 割がた「どこかがバージョン違い」に帰着します。

その3 — 入力 JSON のスキーマテストを書く。Claude Code のバージョンアップで、Hooks に渡される JSON の形式が少し変わることがあります。自分のスクリプトが期待しているキーが欠けていたら即座に気付けるよう、Layer 2 で保存した JSON に対してスキーマテストをスケジュール実行しておきます。

この3つを習慣化するだけで、「ある日突然動かなくなった」系の事故はほぼ起きなくなります。逆に言うと、これらを入れないまま Hooks を本番運用していると、ある日どこかで大きく止まります。

本番障害の調査記録:実例から学ぶ切り分けフロー

最後に、私が実際に遭遇した本番障害の切り分け手順を 1 件紹介します。症状は「PreToolUse で走らせている secrets scanner が、特定メンバーの環境だけで動かない」というものでした。

最初の 1 時間は「matcher が合っていないのでは」と疑って settings.json を眺めていましたが、手がかりがありません。そこで Layer 1 のフック(呼ばれた事実だけを記録する)を差し込んだところ、問題のメンバーの環境では fire ログが全く残らないことが分かりました。この時点で、スクリプト内部ではなく設定ファイル側の問題に絞れます。

次に settings.json の読み込み順を確認します。Claude Code はユーザー設定、プロジェクト設定、ローカル設定の順でマージされるため、どこかの層で同じキーが上書きされている可能性があります。問題のメンバーのプロジェクト .claude/settings.local.json を開いたところ、古い検証用の hooks 設定が残っていて、PreToolUse をまるごと上書きしていました。これが原因でした。

このケースでポイントだったのは、最初の 1 時間を Layer 1 なしで戦っていたら、恐らく丸一日かかっていただろうということです。事実として「呼ばれていない」が確定した瞬間に、調査範囲が .claude/ 配下の設定ファイルだけに絞られました。

Hooks の本番デバッグは、派手なテクニックよりも地味な可観測性の積み重ねが効きます。ログを取るコストは小さく、ログが取れていない時の調査コストは指数的に大きい。この非対称性を理解しているかどうかが、Hooks 運用の生死を分けると感じています。

本記事のスクリプトはそのままコピーして使えるので、まずは Layer 1 の fire ログだけでも仕込んでみてください。次に Hooks が動かなくなった時、自分に数時間のデバッグ時間を与えているはずです。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Claude Code2026-04-29
Claude Code の実行を OpenTelemetry で観測する — エージェント処理を本番品質で可視化する設計ガイド
Claude Code のエージェント処理を OpenTelemetry でトレースする本番設計ガイド。フック連携・ツール呼び出し計測・MCP 通信・コスト属性・サンプリングまで、実運用で詰まらないパターンを解説します。
Claude Code2026-04-25
Claude Code のシークレット管理とトラスト境界設計 — エージェント時代の秘匿情報ハンドリング本番ガイド
Claude Code がシェルを掌握する時代の秘匿情報設計を、トラスト境界・注入パターン・流出防止フックの3軸で体系化。.env から MCP までの本番運用パターンと、よくある落とし穴を実装コード付きで解説します。
Claude Code2026-07-03
5分の沈黙で誰かがやり直しを始める — streaming idle watchdog 既定化で数え直すリトライの責務
Claude Code の streaming idle watchdog 既定化で、実行スタックに再試行する層がもう一枚増えました。SDK・自前ラッパー・watchdog・スケジューラの4層を棚卸しし、最悪ケースの試行回数を掛け算で見積もり、リトライ責務を一層に畳む設計をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →