はじめに
Claude Codeで作業を終えようとしたら、同じ指摘が何度も表示されて終了できない状態になりました。
しかも、確認すると自分が触っていないプロジェクトの変更を検知しています。
当時のログを調べると、同じワークスペースで動いていた別セッションの編集が、こちらのStop hookに巻き込まれていました。
この記事では、Stop hookがなぜ鳴り続けたのかを整理します。
ログの読み方と、安全側に設計する方法も説明します。
こんな人に向けて書いています
- Claude CodeのStop hookや終了時チェックを自作している人
- 同じワークスペースで複数のAIセッションを動かしている人
- 「自分は変更していないのに、変更を記録しろ」と指摘される人
この記事でわかること
- ファイルの更新時刻だけで変更元を判定する危険性
-
stop_hook_activeがあってもループを止められない場合の切り分け方 - 並行セッションを考慮したStop hookの設計と、記録してはいけない変更の扱い
前提
- Claude Code 2.1.205の当時のログを参照しています。
- Stop hookはPowerShellで実装されています。
- 記録先はプロジェクトごとの履歴、またはワークスペース用の記録です。
何が起きたのか
このStop hookは、ソース側の最新更新時刻が記録側より新しい場合に判定します。
さらに、一定時間内の変更であれば「記録漏れ」と判定するものでした。
ところが、同じワークスペースで別のClaude Codeセッションが動いていました。
別セッションがファイルを更新すると、こちらのStop hookからは「ソースが更新されたのに記録されていない」ように見えます。
実際には、次のような順番で別々のループが発生していました。
- 2026-07-09 20:10 JST:
workspace(計画/skills)を1回検知 - 2026-07-10 00:23:18〜00:25:37 JST:計7回検知
-
miyoki-portfolioを2回 -
workspace(計画/skills)を5回
-
- 2026-07-10 00:39:20〜00:39:56 JST:
proposal-generatorを9回検知
つまり、miyoki-portfolioからproposal-generatorを経由してworkspace(計画)へ移った、という1本のループではありません。
3つの別ループを、同じ問題として見ていたことが混乱の原因でした。
「待機中」を含む発話は6件でした。
そのうち「待機中です。」と完全一致する発話は5件でした。
さらに、終了時チェックの要約ログは38件ありました。
そのうち、hookが実際に出力を出したものは17件でした。
38件すべてが、ログ上は次の状態です。
- 継続阻止フラグ:
false - 通知レベル:
suggestion
体感としては終了を妨げられていました。
しかしログ上は「ブロック」ではありません。
suggestionが繰り返し注入されていた状態です。
いつ起こるのか
共有ワークスペースで複数セッションが同じ監視対象を更新し、hookがファイルの更新元をセッション単位で識別できないときに起こります。
更新時刻だけを見る仕組みでは、誰が変更したかを判定できません。
こちらのセッションが何も触っていなくても、別セッションの更新によって「未記録」と判定されます。
他に起こり得る場面
同じ原理の問題は、次のような場面でも起こります。
- ファイル監視型のビルドツールと、別セッションのコード生成が同じディレクトリを更新する場合
- フォーマッターとlint hookが、同じ保存操作を別々の変更として検知する場合
- テストランナーが生成するカバレッジファイルを、別の終了時チェックが監視する場合
困るポイント
- 自分の変更ではないのに、自分の作業として指摘される
- エラーではなくsuggestionとして記録されるため、実際に何が止まっているのか分かりにくい
- 並行セッションが動いている間だけ発生し、再現しにくい
- 「記録すれば終わる」と見えても、他の作業内容を勝手に記録する危険がある
- 終了時の確認が繰り返され、作業を終えられない状態になる
stop_hook_active の確認だけでは足りない
確認できた範囲では、Stop hookにはstop_hook_activeを扱う無限ループ防止の処理がありました。
入力値がtrueの場合はexit 0になる挙動も確認できました。
stop_hook_activeは、Stop hookが原因で再開されたかを示す入力値です。
exit 0は、スクリプトが正常終了として処理を終えることを意味します。
手元で次の入力を渡すと、実際にexit 0で素通りしました。
# 再開時の無限ループを防ぐため、stop_hook_active: trueを渡します。
echo '{"stop_hook_active":true}' | powershell -File hook.ps1
この結果から、少なくともstop_hook_active: trueを受け取った場合のガード自体は動作していました。
一方で、実際の再開経路でClaude Code側からフラグが渡っているかどうかは、この単体確認だけでは判断できません。
つまり、次の2つを分けて確認する必要があります。
- スクリプトが
stop_hook_activeを正しく処理できるか - Claude Codeが再開時に
stop_hook_activeを渡しているか
スクリプト単体のテストが通っても、実際の再開経路まで直ったとは言えません。
時刻比較だけでは変更元を判定できない
問題の本質は、更新時刻と記録時刻の比較です。
確認できた条件は、最新のソース更新時刻が記録側の最新更新時刻より新しいことでした。
さらに、更新から6時間以内であることも条件でした。
この条件では、監視対象の更新が自分のセッションによるものか、別セッションによるものかまでは判定できません。
今回も、監視対象ファイルの更新時刻は手元のセッションの作業と一致していませんでした。
-
miyoki-portfolioの実績データが40秒前に更新 -
workspace(計画)の計画ファイルも数十秒前に更新 - その時間帯に別のClaude Codeセッションが同じワークスペースで作業
この条件が揃うと、時刻比較だけのhookは変更を誤帰属します。
流れを図で整理する
並行セッション対策を考えるときの注意点
並行セッション対策として、複数のtranscriptや更新時刻を使って判定を弱める設計は考えられます。
たとえば、複数セッションが同時に動いている可能性があるとき、通常の判定をスキップする方法です。
その代わりに、未記録候補だけを表示します。
これは、次のような考え方です。
セッションの重複が確認できない
→ 自分のセッションによる変更かもしれないので判定する
セッションの重複が疑われる
→ 変更元を特定できないため、終了時の強い判定を避ける
ただし、この設計を実装する場合は、公開されている仕様と手元の実測で確認する必要があります。
- 何を基準にセッションの重複を判定するか
- transcriptの更新時刻だけで十分か
- 通常の判定をどの条件でスキップするか
- 結果だけを表示するモードにも同じ判定を適用するか
- その処理がどの経路から呼び出されるか
実装を確認せずに、具体的な時間・件数・呼び出し元を現在の事実として断定するのは危険です。
記録すればよい、とは限らない
今回のような状況では、「履歴に1行書けば終わる」と考えがちです。
しかし、知らないセッションの作業内容を推測して記録すると、記録の正確性が失われます。
記録先が記事の種や開発履歴を兼ねている場合、誤った内容を残すことは安全網そのものを壊します。
今回の判断は、条件が解消するまで待つことでした。
これは、記録漏れを見逃す判断ではありません。
変更元が特定できない状態で、誤った記録を追加しないための判断です。
Stop hookを設計するときの確認項目
Stop hookを作る場合は、次の順番で確認すると切り分けやすくなります。
- hook単体に
stop_hook_active: trueを渡したとき、想定したexit codeになるか - 実際の再開経路でも同じフラグが渡っているか
- 監視対象の更新元をセッション単位で識別できるか
- 複数のセッションが同時に動いている場合の扱いが決まっているか
- 終了を妨げるモードと、結果だけ表示するモードを分けているか
- suggestionと実際のブロックをログ上で区別できるか
- 知らない作業を記録しないための条件があるか
特に重要なのは、hookが出したメッセージの文面だけで判断しないことです。
今回も体感は「ブロック」でした。
しかし、ログ上の継続阻止フラグは全件falseでした。
表示、終了動作、ログのフィールドを分けて調べる必要があります。
自分の変更だけを記録するために
Stop hookは、記録漏れを防ぐための仕組みです。
ただし、共有ワークスペースでは「変更されたこと」と「自分が変更したこと」が一致しません。
ファイルのmtimeだけで判断すると、別セッションの作業を自分の未記録変更として扱います。
安全側に倒すなら、次のような方針が考えられます。
- セッション識別子やtranscriptとの対応を優先する
- 並行セッションを検知したら、終了ブロックをスキップする
- 代わりに結果だけを表示して候補を確認する
- 記録内容が分からない場合は、推測で追記しない
- ログ上のsuggestionと実際のブロックを別々に確認する
Stop hookは強くするほど安全になる、とは限りません。
誤帰属した状態で終了を止め続けると、正しい作業記録を守るための仕組みが、別の作業を壊す仕組みになります。
個人でAIツールや業務自動化を作っているMiyokiといいます。
自分で作って詰まった経験をもとに、同じ罠を避けるための手順を紹介しています。
自分のセッションが触った変更だけを記録したいなら、最初に確認するのは「ファイルが更新されたか」ではありません。
「誰のセッションが更新したか」を確認します。
並行作業を完全に識別できない場合は、終了をブロックするより、候補を表示して人が確認できる形に弱める方が安全です。
今回のように、7回のループと9回のループが別々に存在することもあります。
時刻で束ね、ログの件数とフィールドを照合するだけで、見えていた現象の解像度はかなり変わります。