AIDive

動画パック

Claude Code Mods: fail-open の実験、計測結果、チェックリスト、情報源

10 分で読めます

TL;DR

  • Claude Code Mod は 3 つのファイルでできています。.claude-plugin/plugin.json、{"modules":["./index.ts"]} を持つ hooks/hooks.json、そして register(on) を export する TypeScript モジュールです。ロードに必要なのはこれだけです。
  • ビルド 2.1.272 では、/plugin-types が書き出す型は 11,783 行の claude-code.d.ts です。23 個の noun にまたがる 84 個のイベント名または call 名があります。fs.readFile は消え、noun は fs.read と fs.write になりました。
  • 42 行の tool.call フックにより、モデルは Read ツールでも Bash ツールでも本物のキーではなく API_KEY=[REDACTED] を読みました。正常時 1 ホップあたり 27.9 ms で、セッションに加わるトークンは約 0 です。
  • 10 秒の予算を超えてスリープするフック、または例外を投げるフックはスキップされ、その下のコマンドが実行されます。ログには出ますが、実際には素通りです。ガード用の Mod は fail open します。
  • 生成パスは機能します。1 文から 190 行の Mod と 53 行のテストが約 4 分、$1.23 で生成され、claude plugin validate は警告なし、claude plugin test は 4 pass / 0 fail でした。
  • 1 点だけ再現しませんでした。Mod は claude -p ではロードされましたが、pty 経由のインタラクティブ REPL では 2 回試しても無反応でした。REPL でのロードは、実際のターミナルで確認するまで未検証として扱ってください。

計測が示すこと

以下はすべて、CLAUDE_CODE_ENABLE_FUNCTION_HOOKS を設定した Claude Code 2.1.272 で、手書きの redact-secrets という Mod と、それを壊すために作った 3 つの使い捨て Mod を使って実行したものです。この機能自体は Function Hooks の提案 issue で追跡されており、今でも公式仕様に最も近いものです s3。

ツールはドキュメントより先に存在します。ビルド 2.1.272 には claude plugin validate、test、eval、details が含まれています。plugin --help のコマンド一覧には載っていませんが、test は動きます s3。セッション内で /plugin-types を実行すると、.claude/types/claude-code.d.ts に 11,783 行が書き出され、さらに 7 つのサーバーの 150 個の MCP ツールをカバーする 3,438 行の claude-code-mcp.d.ts も作られます s3。生成された型を数えると、23 個の noun に 84 個のイベント名または call 名があります。9 月の記事で読んだかもしれない点が 1 つ変わっています。fs.readFile はもう存在せず、使えるのは fs.read と fs.write です s9。同じ型によれば、tool.call フックは { result, context? } または { deny } を返します。text と ref は core 由来でフック自身の戻り値には含まれないため、書き換えるのは text ではなく result です s3。

claude plugin validate は、Mod が一度も動く前にフットプリントを表示します。./index.ts hooks: tool.call と ./index.ts calls: $.ui.toast、モジュールがホストの機能に触れない場合は calls: nothing on $ です s4。この静的な 1 行は、awesome-claude-code-mods のようなディレクトリが現時点で自動化できる唯一の審査であり、次の段落を読むときに重要になります。

リダクションは -p モードで動きます。42 行のフックが tool.call を横取りし、ファイルとシェル出力に sk-test1234567890abcdef が入っていた箇所で、モデルは Read ツールでも Bash ツールでも API_KEY=[REDACTED] を受け取りました s3。デバッグログでは、正常な 1 ホップは往復 27.9 ms で、ワーカーへのホップと next() を含みます s3。claude plugin details はこの Mod が全セッションに加えるトークンを約 0 と見積もりました。Mod はプロンプトのテキストではなくプロセス内のコードであり、これがシェルフックやスキルに対する推進派の主な論拠です s7。

ガードは fail open します。15 秒スリープする slow-guard という Mod は 10 秒の予算で打ち切られ、hook failed: slow-guard: exceeded 10000ms budget (tool.call; skipped; what is below it ran in its place) と出ましたが、echo hi は実行されました s3。例外を投げる throw-guard という Mod も同じようにスキップされ、hook failed: throw-guard: boom (tool.call; skipped; what is below it ran in its place) と 574.2 ms が報告されました s3。ログでは騒がしいのに、実際には素通りです。何かをブロックするための Mod は、この点を踏まえて読む必要があります。ガードのバグはクラッシュではなく穴になります。

生成は安上がりです。1 文から、モデルは動作する 190 行の Mod と 53 行のテストを約 4 分、34 ターン、$1.23 で書きました(出力トークン 19,053、キャッシュ読み取り 497,282、thinking 8,357)s3。その Mod は claude plugin validate を警告なしで通り、claude plugin test は 0.31 秒で 4 pass / 0 fail、実際にキーを隠しました s3。手書きの Mod では、claude plugin test は API キーもモデル呼び出しもなしで、0.25 秒で 1 pass でした s3。テンプレートで同じ実験を繰り返したいなら、エージェントに Mod の書き方を教えるスキルがすでにあります s12。

再現しなかったこと。pty 経由の REPL では 2 回とも Mod がロードされなかったため、$.ui.toast は一度も表示されませんでした。-p では、同等のものがデバッグ行として出ただけです。X で出回っている「REPL のみ」という主張とは逆の結果が観察され、根本原因は特定できていません s8。ワーカーが固まったときの 5 秒ハートビートもテストしていません。測定したのは 10 秒の await 予算と例外パスだけです。

計測結果

ケース Mod の動作 結果 時間
redact-secrets、Read ツール tool.call で result を書き換える モデルには API_KEY=[REDACTED] が見える 1 ホップ 27.9 ms
redact-secrets、Bash ツール 同じフック、シェル出力 モデルには API_KEY=[REDACTED] が見える 1 ホップ 27.9 ms
slow-guard tool.call 内で 15 秒スリープ スキップされ、echo hi が実行された 10000 ms で打ち切り
throw-guard tool.call 内で boom を投げる スキップされ、コマンドが実行された 574.2 ms
生成された Mod 1 文から 190 行 + テスト 53 行 validate: 警告なし、test: 4 pass / 0 fail 約 4 分、34 ターン、$1.23
redact-secrets への claude plugin test API キーなし、モデル呼び出しなし 1 pass 0.25 s
claude plugin details Mod のセッションコスト 約 0 トークン追加 n/a

手順: Claude Code 2.1.272、環境変数で function hooks を有効化。各 Mod は plugin.json、hooks/hooks.json、1 つの index.ts を持つプラグインディレクトリです。実行はデバッグログを有効にした claude -p で行い、用意したファイルとシェルコマンドの両方に sk-test1234567890abcdef を含めました。fail open のケースは、15 秒スリープする Mod と例外を投げる Mod で動かし、ガード対象のコマンドは echo hi です。インタラクティブ REPL は pty 経由で操作し、2 回とも Mod はロードされませんでした。

月曜にやること

  • セッションで /plugin-types を実行し、.claude/types/claude-code.d.ts を開く。9 月の記事のスニペットを信じる前に、fs.read と tool.call を検索する。
  • 3 ファイルの Mod の骨組み(plugin.json、{"modules":["./index.ts"]} を持つ hooks/hooks.json、register(on) を export する index.ts)を書き、claude plugin validate を実行する。hooks: と calls: のフットプリント行を読む。
  • いちばんよく使うシェルフックを、result を書き換える tool.call ハンドラに移植し、デバッグログのホップ時間をシェル版と比べる。
  • Mod の隣に claude plugin test のファイルを追加し、CI で API キーなしでガードが動くようにする。
  • すべてのガードハンドラを try/catch で包み、失敗時は { deny } を返す。このビルドでは、例外や 10 秒の停止でガードがスキップされ、コマンドが通ってしまう。
  • Mod を claude -p と実際のインタラクティブなターミナルで別々にテストし、どちらでロードされたかを記録する。
  • サードパーティの Mod をインストールする前に claude plugin validate を実行し、Mod が使う理由のないホスト機能を calls: 行に挙げているものは断る。

さらに読む

  • 提案 issue を、コメントに添付されたアーキテクチャ PDF も含めて最後まで読む。register(on)、予算、ワーカーへのホップについての唯一の文書化された契約です s3。
  • Command Code Mods の設計と比べる。ホストプロセス内の ModApi に対して TypeScript を実行するもので、2 つのシステムは形も early access の制限も共通です s2。
  • claudefa.st のプレビューは、機能がまだ提案だった頃に書かれています。9 月 3 日の文章と 2.1.272 のバイナリの間で何が変わったかを見るのに役立ちます s9。
  • Prathkum のノートツイートは、プロセス内フックがトークンとレイテンシでシェルフックに勝つ理由を、最も短く明快に述べています s7。
  • cc-mod-waitwhat は最初に読む Mod として良い例です。プロンプトの上に UI を出し、トランスクリプトには何も書き込みません s11。
  • cc-arcade は $.ui がどこまで届くかを示します。Mod からプロンプトの上にゲームを描画しています s5。
  • フックのリファレンスは今もシェルモデルを説明しています。古い各イベントを新しい noun.event 名に対応づけるため、開いておいてください s1。
  • X の懐疑派は、プラグインですでに同じことができ、リリースのたびに表面が壊れると主張しています。fs noun の改名はその主張を支える 1 つのデータポイントです s23。

情報源

  • Function Hooks proposal (issue #91870), GitHub, anthropics/claude-code. 読む理由: Mod についての仕様に近い唯一の文書で、コメントにアーキテクチャ PDF と出荷までの経緯があります。
  • Hooks reference, Claude Code docs. 読む理由: 移行元のシェルフックのモデルを、イベントごとに説明しています。
  • Command Code Mods documentation, Command Code. 読む理由: プロセス内 TypeScript という同じ形の先行例で、Anthropic が何を真似し何を避けたかを見分けるのに役立ちます。
  • awesome-claude-code-mods, GitHub, karanb192. 読む理由: 公開 Mod を自動スキャンしたディレクトリで、審査は validate のフットプリントだけです。
  • cc-arcade, GitHub, sezaakgun. 読む理由: この機能を目に見えるものにしたデモで、$.ui の見本市でもあります。
  • Boris Cherny announcement tweet, X, Boris Cherny. 読む理由: ブログ記事がないため、Anthropic のエンジニアによる唯一のローンチ声明です。
  • Prathkum: Function Hooks explained, X, Prathkum. 読む理由: すでにシェルフックを書いている人向けの、フックと Mod の違いの最良の短い説明です。
  • shipnotesai reaction thread, X, shipnotesai. 読む理由: 「REPL のみ」という主張が広まった場所で、私たちの実行はそれと矛盾しました。
  • Claude Code Function Hooks: Preview Behind a Flag, claudefa.st. 読む理由: 出荷前の解説で、提案とバイナリを突き合わせるのに向いています。
  • cc-mod-waitwhat, GitHub, GGGODLIN. 読む理由: トランスクリプトではなく UI に書き込む、小さくて読みやすい Mod です。
  • claude-mods-skill, GitHub, BeLazy167. 読む理由: 1 文の実験を繰り返したい人向けの、Mod 生成用スキルです。
  • AxialisSoftware reaction, X, AxialisSoftware. 読む理由: 懐疑的な立場を 1 つのツイートにまとめています。

FAQ

Mod は今すぐシェルフックの代わりになりますか?

ブロックが必要なものについては、まだです。2.1.272 では、例外を投げるか 10 秒を超えて止まるフックはスキップされ、コマンドが実行されます。シェルフックは引き続き動くので、宣言的な catch や fail closed のオプションが入るまで、ブロック用のものはそちらに置いておいてください。

Mod が問題なく動くなら、なぜ validate が重要なのですか?

hooks: と calls: の行が、Mod が $ 上で何に触れるかを示す唯一の静的ビューだからです。自作の Mod ならフットプリントの確認になり、サードパーティの Mod なら、コードが自分のプロセスで動く前に得られる審査のすべてです。

Mod は 1 セッションあたりいくらかかりますか?

claude plugin details は約 0 トークンの追加と報告しました。Mod はプロンプトのテキストではなくエンジン内で動くコードであり、スキルや CLAUDE.md のルールに対する主な利点です。

なぜ Mod は -p ではロードされたのに REPL ではロードされなかったのですか?

不明です。pty 経由の 2 回の試行は無反応で、claude -p ではロードされてフックが適用されました。原因は機能ではなく pty 環境にある可能性が高いので、どちらの結果も頼る前に自分のターミナルでテストしてください。