Skip to content

feat(hooks): Stop hook による tool call leak 検知を追加 (ADR-053) - #261

Merged
aloekun merged 1 commit into
masterfrom
feat-stop-tool-call-leak-hook
Jul 12, 2026
Merged

feat(hooks): Stop hook による tool call leak 検知を追加 (ADR-053)#261
aloekun merged 1 commit into
masterfrom
feat-stop-tool-call-leak-hook

Conversation

@aloekun

@aloekun aloekun commented Jul 12, 2026

Copy link
Copy Markdown
Owner

概要

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 件):

セッション (先頭8桁) leak 件数 化けたプレフィックス行
87387df2 59+1 court
931b72e4 90 court
05c197f1 35 count
025e5aeb 13 code
  • leak 発生時は turn がそのまま終了し作業が停止する
  • ハーネスの自動リカバリ (isMeta の "Your tool call was malformed") は計 15 件程度しか発動せず、大半はユーザーの手動指摘で復旧していた
  • 指摘後の再試行でも再 leak するケースを複数観測

変更内容

  • 新規 crate src/hooks-stop-tool-call-leak/: transcript 末尾 200 行から isSidechain でない最後の assistant エントリの text block を検査。fence 除外 + 行頭 <invoke name=" アンカー + 構造行 (行頭 </invoke> / <parameter name=") の組で判定
  • Stop 配列の先頭に登録 (settings.local.json.template、timeout 5s)
  • hooks-config.toml[stop_tool_call_leak] (本 repo は dogfood のため enabled = true、code default は OFF)
  • ADR-053 起草 + CLAUDE.md 索引追加

設計判断 (意図的逸脱 2 点、詳細は ADR-053)

  1. ループ防止に stop_hook_active skip を不採用 (ADR-004 逸脱): 品質ゲート retry 中の leak を取り逃がすため + 再 leak 実績があるため。代わりに連続 leak カウント上限 3 で fail-open (isMeta user はチェーン継続、実ユーザー発話でリセット)
  2. エラー時 fail-open (ADR-043 逸脱): fail-closed だと transcript 読み取り失敗の持続で無限ブロックに陥るため。本 hook はセキュリティゲートではなく UX 復旧装置

テスト / 検証

  • 単体 36 + E2E 7 (実 exe spawn) = 43 tests
  • 実データ検証: メインリポジトリ全セッションログに対し検知 198 / 非検知 2 — 誤検知 0・取り逃がし 0 (非検知 2 件は正当なインラインコード引用・分析的説明文)
  • 配置済み release exe のスモークテスト (block JSON / 無出力) 確認済み
  • pre-push review: security / simplicity とも APPROVE

留意点

Summary by CodeRabbit

  • 新機能

    • ツール呼び出しが実行されずテキストとして漏れた場合に検知し、再実行を促す停止ゲートを追加しました。
    • 連続検知回数の上限を設け、上限到達時は安全に処理を継続します。
    • 検査を無効化できるキルスイッチを追加しました。
  • ドキュメント

    • 検知仕様、制限事項、運用計画に関するドキュメントを追加しました。
  • テスト

    • 通常応答、検知、再試行、上限到達、設定無効化、入力エラーなどを検証するテストを追加しました。

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 に変更
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

テキスト領域へ漏洩したXML形式のツール呼び出しを検知するRust製Stop hookを追加しました。transcript解析、再実行要求、連続漏洩時のfail-open、設定・ビルド統合、ユニットテストとE2Eテストを実装しています。

Changes

Tool call leak検知

Layer / File(s) Summary
仕様と運用設定
docs/adr/adr-053-stop-tool-call-leak-detection.md, docs/stop-tool-call-leak-plan.md, .claude/hooks-config.toml, CLAUDE.md
漏洩の検知条件、block応答、連続ブロック上限、fail-open、kill-switch、試験運用方針を定義しました。
Transcript解析と漏洩検知
src/hooks-stop-tool-call-leak/Cargo.toml, src/hooks-stop-tool-call-leak/src/detect.rs, src/hooks-stop-tool-call-leak/src/transcript.rs
コードフェンス外の<invoke>構造を検知し、transcript末尾からmain-session assistantの連続漏洩とツール名を抽出します。
Stop hook実行フロー
src/hooks-stop-tool-call-leak/src/main.rs
stdin、設定、kill-switch、transcriptを処理し、漏洩時はblock JSON、上限到達時やエラー時はfail-openを返します。
Hook登録とビルド統合
Cargo.toml, package.json, .claude/settings.local.json.template
workspace、全体ビルド、生成先.claude/、Stop hook commandへ新しい実行ファイルを追加しました。
ユニットテストとE2E検証
src/hooks-stop-tool-call-leak/src/detect.rs, src/hooks-stop-tool-call-leak/src/transcript.rs, src/hooks-stop-tool-call-leak/src/main.rs, src/hooks-stop-tool-call-leak/tests/e2e.rs
検知条件、走査規則、設定処理、block・fail-open・kill-switchの挙動を検証します。

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または停止許可
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 変更内容の中心である Stop hook による tool call leak 検知追加を簡潔に表しており、ADR-053 も一致しています。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-stop-tool-call-leak-hook

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: issue_comment (created) / 実行 run
  • CI: GitHub Actions 上の CI check は本 PR に登録なし。唯一の status check は CodeRabbit で IN_PROGRESS (処理中、未完了)。mergeStateStatus: UNSTABLE はこの pending check に起因すると見られる。mergeable: MERGEABLE
  • レビュー状況:
    • CodeRabbit: レビュー処理中。会話コメントは "Currently processing new changes..." のプレースホルダーのみで、actionable な指摘はまだ 0 件。
    • 人間レビュー: なし (reviewDecision 空、pulls/261/reviews は空配列)。
    • インラインコメント: なし (pulls/261/comments は空配列)。
  • Verdict: approved (現時点で applicable な指摘が 0 件、CI 失敗なし。CodeRabbit 完了後に指摘が出れば次回イベントで再評価される)

Applicable Findings (Critical / High / Major)

該当なし (レビュー指摘 0 件)

Applicable Findings (Medium 以下)

該当なし

Filtered (not applicable)

該当なし

diff 概要 (レビュー指摘が無いため軽量サマリー)

13 ファイル変更、新規 crate src/hooks-stop-tool-call-leak/ を追加する ADR-053 (試験運用) の実装 PR。

  • 新規 Stop hook: main.rs (294行、hook entrypoint / config読込 / kill-switch env STOP_TOOL_CALL_LEAK_OVERRIDE)、detect.rs (170行、行頭アンカーによる tool-call leak 検知ロジック)、transcript.rs (254行、transcript JSONL tail 走査 + 連続 leak カウントによるループ防止)、tests/e2e.rs (187行、実 exe spawn による 7 E2E テスト)
  • 設定 / 配線: .claude/hooks-config.toml[stop_tool_call_leak] section 追加 (opt-in, enabled = true は本 repo dogfood 用)、.claude/settings.local.json.template の Stop 配列先頭に hook 登録、Cargo.toml/Cargo.lock に workspace member 追加、package.json に build script 追加
  • ドキュメント: docs/adr/adr-053-stop-tool-call-leak-detection.md (新規 ADR、177行)、CLAUDE.md に索引追加、docs/stop-tool-call-leak-plan.md (一時作業計画ファイル、175行、ファイル冒頭に「WP-7 完了時に削除」と明記)
  • ADR-039 (experimental標準パターン: opt-in / kill-switch / bounded lifetime) 準拠を ADR 本文中で自己申告しており、構成上は他の試験運用 ADR (ADR-029, ADR-030 等) と一貫している。

次のアクション

  • CodeRabbit のレビュー完了を待つ必要はない (指示通り未着のまま今回の分析は終了)。指摘が出た場合は次回イベントで本 workflow が再分析する。
  • docs/stop-tool-call-leak-plan.md は PR 自身の WP-7 チェックリストで「作業完了時に削除」と明記された一時ファイルだが、この diff にはまだ残っている。マージ前に削除漏れでないか確認することを推奨する。
  • mergeStateStatus: UNSTABLE は CodeRabbit の pending 状態に起因する可能性が高いため、マージ前に CodeRabbit のレビュー完了を確認する。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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

📥 Commits

Reviewing files that changed from the base of the PR and between cdf340a and 9505e80.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .claude/hooks-config.toml
  • .claude/settings.local.json.template
  • CLAUDE.md
  • Cargo.toml
  • docs/adr/adr-053-stop-tool-call-leak-detection.md
  • docs/stop-tool-call-leak-plan.md
  • package.json
  • src/hooks-stop-tool-call-leak/Cargo.toml
  • src/hooks-stop-tool-call-leak/src/detect.rs
  • src/hooks-stop-tool-call-leak/src/main.rs
  • src/hooks-stop-tool-call-leak/src/transcript.rs
  • src/hooks-stop-tool-call-leak/tests/e2e.rs

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: pull_request_review (submitted) / 実行 run
  • CI: analyze (GitHub Actions) は pending (未完了)。CodeRabbit status check は pass (4m48s)。mergeStateStatus: UNSTABLEmergeable: MERGEABLE
  • レビュー状況:
    • CodeRabbit: review submitted (state: COMMENTED、2026-07-12T06:41:06Z、commit 9505e808)。Nitpick 2 件(Trivial / Low value)。approve/request-changes ではなくコメントのみ。
    • 人間レビュー: なし (reviewDecision 空、pulls/261/reviews に人間レビューなし)。
    • インラインコメント (pulls/261/comments): 0 件 (nitpick 2 件は review body 内に埋め込まれており、別途のインラインコメントとしては登録されていない)。
    • 前回分析 (issue_comment、06:38:10Z) 以降に本 CodeRabbit review (06:41:06Z) が新規追加されているため重複ガード非該当、再分析を実施。
  • Verdict: approved (Critical/High/Major の applicable finding 0 件。applicable な finding は Trivial(Low) 1 件のみ、もう 1 件は intentional design により not_applicable)

Applicable Findings (Critical / High / Major)

該当なし

Applicable Findings (Medium 以下)

# File (Line) Reviewer Issue Recommended Action
1 src/hooks-stop-tool-call-leak/src/main.rs:187-195 (emit_block) CodeRabbit (Trivial/Low value) serde_json::to_string 失敗時に block JSON が出力されないが (ADR-053 の意図的 fail-open 通り)、stderr にも何も出ず観測性が低い 任意対応: Err 分岐で eprintln! による診断メッセージを追加 (fail-open の挙動自体は変更しない)。ADR-053 の fail-open 方針と矛盾しないため優先度は低い

Filtered (not applicable)

# File (Line) Issue Filter Reason
2 docs/stop-tool-call-leak-plan.md:1-5 一時計画ファイルを本 PR の最終 commit で削除すべき、との提案 Intentional design: ファイル冒頭および WP-7 チェックリストで「push/PR 完了後にファイル自体を削除」と明記済み (- [ ] push / PR- [ ] 本ファイル削除 の順)。CodeRabbit のコメント自体も "PR objectives も intended for deletion after merge と記載" と認めており、著者の既定計画通りの状態。マージ前削除を強制する根拠なし

次のアクション

  • Medium 以下 1 件 (emit_block の stderr 診断メッセージ追加) は任意対応。対応する場合は次のローカルセッションで src/hooks-stop-tool-call-leak/src/main.rsemit_block に 1 行追加し、cargo test -p hooks-stop-tool-call-leak で回帰確認。
  • analyze check が pending のままのため、完了を待って結果を確認すること (本分析では待機・ポーリングは行っていない)。
  • docs/stop-tool-call-leak-plan.md の削除は WP-7 の計画通り、push/PR 承認後・マージ後のタイミングで対応で問題ない。

@aloekun
aloekun merged commit 6ab6471 into master Jul 12, 2026
2 checks passed
@aloekun
aloekun deleted the feat-stop-tool-call-leak-hook branch July 12, 2026 08:22
aloekun added a commit that referenced this pull request Jul 12, 2026
ADR-053 (Stop hook による tool call leak 検知) の実装が PR #261 でマージ
されたため、セッション引き継ぎ用の一時計画ファイルを予定どおり削除する。
ファイル自身のヘッダおよび PR #261 の説明文で「作業完了時に削除」と
明記していた残作業の実施。設計判断・調査結果は ADR-053 に永続化済み。
aloekun added a commit that referenced this pull request Jul 12, 2026
ユーザー承認済みの採用候補 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant