feat(hooks): Stop hook による tool call leak 検知を追加 (ADR-053) - #261
Conversation
Claude Code がツール呼び出しを tool_use block ではなくテキスト領域に生 XML (`<invoke ...>`) として出力し、実行されないまま turn が終了する不具合への対策。 直近 4 セッションで 197 件観測し、ハーネスの自動リカバリは少数しか発動せず ユーザーの手動指摘で復旧していた。 - 新規 crate src/hooks-stop-tool-call-leak: transcript 末尾の最後の assistant エントリの text block を検査 (fence 除外 + 行頭 <invoke name=" アンカー + 構造行)。実データ 200 件で leak 198 / 正当引用 2 を完全分離 (誤検知 0) - 検知時は decision:block で正規のツール呼び出しによる再実行を誘導 - ループ防止: stop_hook_active skip ではなく連続 leak カウント上限 3 で fail-open (ADR-004 からの意図的逸脱、再 leak 実績があるため) - エラー時 fail-open (ADR-043 からの意図的逸脱、無限ブロック回避) - ADR-039 準拠: code default OFF / 本 repo enabled=true / env STOP_TOOL_CALL_LEAK_OVERRIDE kill-switch / bounded lifetime (上流修正確認 or leak 4 週間非観測で撤去判定) - Stop 配列の先頭に登録 (timeout 5s)、単体 36 + E2E 7 tests - docs/stop-tool-call-leak-plan.md は一時作業ファイル (マージ後の削除を予定) ※ 当初 ADR-052 で起草したが PR #260 (自律実行境界) が先に採番したため 053 に変更
📝 WalkthroughWalkthroughテキスト領域へ漏洩したXML形式のツール呼び出しを検知するRust製Stop hookを追加しました。transcript解析、再実行要求、連続漏洩時のfail-open、設定・ビルド統合、ユニットテストとE2Eテストを実装しています。 ChangesTool call leak検知
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ClaudeCode
participant StopHook
participant Transcript
ClaudeCode->>StopHook: Stop event JSONを送信
StopHook->>Transcript: transcript末尾を読み込む
Transcript-->>StopHook: 連続leakとツール名を返す
StopHook-->>ClaudeCode: block JSONまたは停止許可
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 PR Monitor 分析 (GitHub Actions バックストップ)
Applicable Findings (Critical / High / Major)該当なし (レビュー指摘 0 件) Applicable Findings (Medium 以下)該当なし Filtered (not applicable)該当なし diff 概要 (レビュー指摘が無いため軽量サマリー)13 ファイル変更、新規 crate
次のアクション
|
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/hooks-stop-tool-call-leak/src/main.rs (1)
187-195: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value
emit_blockのシリアライズ失敗時に stderr メッセージを追加
serde_json::to_stringが失敗した場合、block が出力されず fail-open となります。これは ADR-053 の fail-open 設計と整合していますが、stderr にメッセージがないとデバッグが困難になります。2 つの文字列のシリアライズ失敗は極めて稀ですが、1 行追加で観測性が向上します。🔍 提案する修正
fn emit_block(reason: &str) { let decision = BlockDecision { decision: "block".to_string(), reason: reason.to_string(), }; - if let Ok(json) = serde_json::to_string(&decision) { - println!("{}", json); + match serde_json::to_string(&decision) { + Ok(json) => println!("{}", json), + Err(e) => eprintln!("[stop-tool-call-leak] block JSON 出力失敗 (fail-open): {}", e), } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks-stop-tool-call-leak/src/main.rs` around lines 187 - 195, Update emit_block to handle the Err case from serde_json::to_string by writing a concise diagnostic message to stderr, while preserving the existing fail-open behavior of emitting no block output.docs/stop-tool-call-leak-plan.md (1)
1-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value一時ファイルの削除タイミング確認
本ファイルは WP-7 で「削除」と明記されており、PR objectives も "intended for deletion after merge" と記載しています。merge 後に削除する計画で問題ありませんが、main ブランチに一時ファイルが残る期間を最小化するため、可能であれば本 PR の最終 commit で削除することを推奨します。
Based on learnings: このリポジトリの docs/ 配下のタイムスタンプは UTC または JST のいずれかで文書内で一貫していればよい。本ファイルは "2026-07-12" で一貫しているため問題なし。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/stop-tool-call-leak-plan.md` around lines 1 - 5, WP-7で削除予定の一時計画ファイルなので、マージ後まで残さず、本PRの最終コミットでdocs/stop-tool-call-leak-plan.md自体を削除してください。Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@docs/stop-tool-call-leak-plan.md`:
- Around line 1-5:
WP-7で削除予定の一時計画ファイルなので、マージ後まで残さず、本PRの最終コミットでdocs/stop-tool-call-leak-plan.md自体を削除してください。
In `@src/hooks-stop-tool-call-leak/src/main.rs`:
- Around line 187-195: Update emit_block to handle the Err case from
serde_json::to_string by writing a concise diagnostic message to stderr, while
preserving the existing fail-open behavior of emitting no block output.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 39017378-213a-471c-b835-de6cd2b85594
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
.claude/hooks-config.toml.claude/settings.local.json.templateCLAUDE.mdCargo.tomldocs/adr/adr-053-stop-tool-call-leak-detection.mddocs/stop-tool-call-leak-plan.mdpackage.jsonsrc/hooks-stop-tool-call-leak/Cargo.tomlsrc/hooks-stop-tool-call-leak/src/detect.rssrc/hooks-stop-tool-call-leak/src/main.rssrc/hooks-stop-tool-call-leak/src/transcript.rssrc/hooks-stop-tool-call-leak/tests/e2e.rs
🤖 PR Monitor 分析 (GitHub Actions バックストップ)
Applicable Findings (Critical / High / Major)該当なし Applicable Findings (Medium 以下)
Filtered (not applicable)
次のアクション
|
ユーザー承認済みの採用候補 4 件を todo13.md + todo-summary.md に登録: - 順位 271 (T1): hooks-stop-tool-call-leak emit_block serialize 失敗時の stderr 診断追加 (fail-open observability、XS) - 順位 272 (T1): cli-docs-lint に ADR 重複採番 + CLAUDE.md 索引整合チェック (PR #261 の ADR-052/053 採番衝突の再発防止。順位 135 placeholder policy は todo entry 側ルールで相補、重複ではないことを登録前に確認済み) - 順位 273 (T2): hooks-stop-tool-call-leak E2E の config 具体値 assert (3 ソース独立指摘、ADR-041 パターン) - 順位 274 (T3): dev-conventions に外部 fixture 参照テストの値 assert convention 追記 (順位 273 の一般化) 採番 note: 当初 265-268 で登録したが、default workspace (メインセッション) の 未 push commit が PR #258-260 feedback 分を順位 265-270 で登録済みと判明 (ADR-045 並列 workspace の採番 race)。本 commit を 271-274 に振り直して回避。 この採番 race 自体の機械検知は順位 272 の adr_consistency lint と同根の課題。 却下 3 件 (analyzer 推奨どおり) と様子見 2 件は登録なし。 PR #262 の feedback report (262.md) の採用候補 4 件は本 4 件と同一内容の 再導出であることを確認済み (新規登録なし)。 feedback report: .claude/feedback-reports/261.md (.gitignore 内部 artifact)
概要
Claude Code がツール呼び出しを正規の tool_use block ではなくテキスト領域に生 XML (
<invoke ...>) として出力し、実行されないまま turn が終了する不具合への対策として、Stop hook で leak を検知しdecision: blockで正規の再実行を促すhooks-stop-tool-call-leakを追加します (ADR-053、試験運用)。背景 (実データ)
直近 4 セッションの transcript 調査で 197 件の実 leak を確認 (調査翌日にはさらに 1 件発生し 198 件):
courtcourtcountcodeisMetaの "Your tool call was malformed") は計 15 件程度しか発動せず、大半はユーザーの手動指摘で復旧していた変更内容
src/hooks-stop-tool-call-leak/: transcript 末尾 200 行からisSidechainでない最後の assistant エントリのtextblock を検査。fence 除外 + 行頭<invoke name="アンカー + 構造行 (行頭</invoke>/<parameter name=") の組で判定settings.local.json.template、timeout 5s)hooks-config.tomlに[stop_tool_call_leak](本 repo は dogfood のためenabled = true、code default は OFF)設計判断 (意図的逸脱 2 点、詳細は ADR-053)
stop_hook_activeskip を不採用 (ADR-004 逸脱): 品質ゲート retry 中の leak を取り逃がすため + 再 leak 実績があるため。代わりに連続 leak カウント上限 3 で fail-open (isMeta user はチェーン継続、実ユーザー発話でリセット)テスト / 検証
留意点
docs/stop-tool-call-leak-plan.mdは一時作業ファイルです (セッション破損時の引き継ぎ用)。マージ後に削除予定Summary by CodeRabbit
新機能
ドキュメント
テスト