◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/Claude Code
⟐ Claude Code/2026-04-25中級

Claude Code Hooksが無限ループする問題 — PostToolUseが自己発火するときの止め方

Claude Code の Hooks を仕掛けたら、ツール呼び出しが止まらなくなった。PostToolUse が自分で書き換えたファイルを再び拾って延々と発火し続ける、典型的な無限ループの原因と止め方を実例ベースで解説します。

claude-code132hooks22troubleshooting61automation55fix11

「Claude Code に prettier の自動整形を仕込んだら、Edit と PostToolUse が交互に発火し続けて、ターミナルが滝のように流れて止まらなくなった」 — Hooks をある程度使い込んでいる方なら、一度は経験したことがあるのではないでしょうか。私自身、最初に PostToolUse と PreToolUse を本格的に使ったときに、まさにこの状態に陥り、Ctrl+C を連打して状況を止めたことを今でも覚えています。

Hooks の無限ループは、ドキュメントには「気をつけてください」程度しか書かれていませんが、実際には設定ファイルを少し書き換えただけで誰でも踏める落とし穴です。ループが起きる仕組みを順を追って整理しつつ、私が普段使っている止め方のパターンを順番にご紹介します。

なぜ Hooks は無限ループするのか

PostToolUse は、Claude Code が何らかのツール(Edit / Write / Bash など)を実行した直後に呼ばれるイベントです。ここで「実行されたツールの結果に応じて、別のコマンドを叩く」という設計が自然にできます。問題は、その「叩いたコマンド」自体が、Claude から見て新たなツール呼び出しとして観測されたり、ファイル状態を変えてしまったりするときに起こります。

典型的なパターンは次の3つです。

  • 整形系の自動化が再発火を呼ぶ: Edit のあとに prettier --write を実行 → ファイルが書き換わる → Claude がその差分を見て「もう一度 Edit が必要」と判断 → さらに PostToolUse が発火、というループ。
  • git 系コマンドが新しい Bash 呼び出しに化ける: PostToolUse(Write) のフックの中で git add . && git commit を実行している場合、フック自身がシェル経由で動くため、ログ上は Bash 呼び出しに見えてしまい、これに反応する別の Bash 用 PostToolUse が二次発火する設計だと連鎖します。
  • マーカーファイルの更新が Edit 扱いになる: チェックポイントやログ用の .claude-last-run.json を書き出すフックを仕掛けると、その書き込みを別の監視系フックが拾ってさらに発火する、という同型の事故もよく見ます。

共通しているのは「フックの副作用が、フックの発火条件と被っている」という構造です。発火条件と副作用が重なる限り、論理的にループが止まる理由はありません。

まず止血する — 暴走しているフックを安全に切る

ループ中の Claude Code は、Ctrl+C 一発では止まらないことがあります。プロセスとしては動き続けて、画面だけが止まる、というケースもあります。私が最初にやることはいつも同じで、settings.json のフック定義を 物理的に空にする ことです。

# 一時的に hooks を空にして暴走を止める
cd ~/.claude
cp settings.json settings.json.bak
jq '.hooks = {}' settings.json > settings.json.tmp && mv settings.json.tmp settings.json

~/.claude/settings.json のほか、プロジェクト直下の .claude/settings.json や .claude/settings.local.json にも hooks が書かれていることがあります。深いリポジトリだと複数のスコープが合成されるので、検索コマンドで一度に確認しておくと安心です。

grep -rln '"hooks"' ~/.claude .claude 2>/dev/null

止血したら、バックアップから定義を1行ずつ戻して、どのフックが暴走の起点だったかを特定します。

ループを設計レベルで防ぐ3つのパターン

止血のあとは、同じ事故を二度と起こさないための恒久対策です。私が実プロジェクトで使っているパターンを3つ紹介します。

パターン1: 「再発火マーカー」で1回限りに制限する

最もシンプルで効果が大きいのが、フック側に「自分が原因の変更には反応しない」フラグを持たせる方法です。環境変数 CLAUDE_HOOK_DEPTH を見て、すでにフックが起動中なら早期 return します。

#!/bin/bash
# .claude/hooks/format.sh — 整形フックの再発火を防ぐテンプレート
if [ -n "${CLAUDE_HOOK_DEPTH:-}" ]; then
  # フック内から呼ばれた処理は、もう一段ネストしない
  exit 0
fi
export CLAUDE_HOOK_DEPTH=1
 
# 本来の処理
prettier --write "$1" >/dev/null 2>&1
 
# 期待出力: 何も標準出力に出さない(Claude が再判断する材料を与えない)
exit 0

ポイントは「フック由来の変更は Claude に通知しない」ことです。フックから echo で結果を吐くと、Claude は「次のステップを考える材料」として読み込んでしまい、ループを誘発します。整形系のように 副作用が成功している限り報告不要 な処理は、stdout を空に保つだけでループ確率がぐっと下がります。

パターン2: matcher を絞ってフック自身の操作を除外する

Hooks 設定の matcher には正規表現が書けます。ここを丁寧に絞り込んでおくと、「フック内で実行した Bash」は対象外、といった選別が可能です。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": ".claude/hooks/format.sh",
        "exclude_paths": [".claude-last-run.json", "node_modules/**", ".next/**"]
      },
      {
        "matcher": "Bash",
        "command": ".claude/hooks/log-bash.sh",
        "exclude_commands_matching": "^(git |prettier|eslint|jq )"
      }
    ]
  }
}

exclude_commands_matching のような除外条件は、暴走を防ぐと同時にログも見やすくなります。Hooks の挙動が想定と違う場合は、まず matcher の評価順を疑うのが基本です。詳細な発火条件の整理は Claude Code Hooksが発火しない・設定が反映されないときの原因と解決方法 に整理しているので、合わせて確認してみてください。

パターン3: ファイルロックで「同時に1つだけ」に制限する

最後の保険は、フックを flock で直列化することです。整形・コミット・通知のように、複数のフックが同じファイル群を触る場合、ロックを取って実行すれば多重発火しても順番待ちになるだけで、ループにはなりません。

#!/bin/bash
# .claude/hooks/safe-format.sh
LOCK_FILE="/tmp/claude-hooks.lock"
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
  # 既に走っていれば諦める(=多重実行を許さない)
  exit 0
fi
 
prettier --write "$1" >/dev/null 2>&1
# 期待挙動: ロックは exit 時に自動解放

ロック方式は副作用の重複を許さない代わりに、長時間処理を握ると後続のフックが落ちます。私は整形のような数百ms単位の処理にだけ使い、長い処理にはマーカー方式(パターン1)を併用しています。

ループの兆候を早めに掴むためのログ設計

無限ループは、起きてから気づくのではなく「起きそうな兆候」のうちに気づきたい現象です。私はフック側に 連続発火回数 のカウンタを仕込み、ある閾値を超えたら自動で止まる仕組みにしています。

#!/bin/bash
# 連続発火を検知して自爆するフック
COUNT_FILE="/tmp/claude-hook-count"
NOW=$(date +%s)
LAST=$(cat "$COUNT_FILE.time" 2>/dev/null || echo 0)
COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0)
 
if [ $((NOW - LAST)) -lt 5 ]; then
  COUNT=$((COUNT + 1))
else
  COUNT=1
fi
echo "$COUNT" > "$COUNT_FILE"
echo "$NOW" > "$COUNT_FILE.time"
 
if [ "$COUNT" -ge 10 ]; then
  echo "⚠️ Hook が5秒間に10回連続発火しました。多重実行の疑いがあるためスキップします。" >&2
  exit 0
fi
 
# ここから本来の処理

このようなセーフティを入れておけば、設定ミスで多重発火しても、被害が「画面が少し騒がしい」程度で済みます。本番運用での Hooks のトラブルシューティングについては、Claude Code Hooks の本番デバッグ術 でさらに踏み込んだ可観測性の話をまとめています。

全体を振り返って — 今日できる一歩

無限ループを完全になくす特効薬はありません。ただ、「フックの副作用が、フックの発火条件と被らないように設計する」という一点を意識するだけで、事故率は劇的に下がります。今日まず手を動かすなら、次の3つのうちどれか1つだけで構いません。

  1. 既存の PostToolUse フックを開いて、stdout に何かを出していないか確認しましょう。整形系なら、出力は空にしてください。
  2. matcher の exclude_paths か exclude_commands_matching を1つ追加して、フック自身の副作用を除外しましょう。
  3. 5秒間で10回以上発火したら自動で止まる、安全装置スクリプトを .claude/hooks/ に1本置いておきましょう。

特に3つ目は、Hooks をこれから複雑にしていく予定があるなら、最初に入れておくべき土台だと考えています。Hooks 全体の設計を見直したくなったら、Claude Code Hooks 自動化マスターガイド の方も参考にしながら、ご自身のワークフローに合うパターンを選んでみてください。

シェア

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

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

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

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

関連記事

⟐ Claude Code2026-05-02
Claude Code の Edit ツールで「old_string is not unique」エラーを止める実践的な書き換え戦略
Claude Code の Edit ツールで「old_string is not unique」が頻発するのは、編集ターゲットの一意性をプロンプト側で保証していないことが根本原因です。再発を防ぐ書き換え戦略を解説します。
⟐ Claude Code2026-05-24
Claude Code で連続する Edit が連鎖的に失敗するときの直し方 — old_string が消える・重複する問題
Claude Code が同じファイルへ Edit を連続で投げると、2件目以降が突然『not found』や『not unique』で落ちることがあります。原因は順序依存とコンテキスト二重化で、Read 再実行と分割パッチで救えます。
⟐ Claude Code2026-05-10
Claude Code の Glob と Grep が0件を返すときの原因切り分け
Claude Code の Glob/Grep ツールがファイルを見つけられないとき、.gitignore除外・隠しファイル・パターン記法の落とし穴を切り分け、適切な代替手段で確実に発見する手順をまとめます。