Claude Modsという名前を見かけた日
Claude Modsは、関数フックを使うClaude Codeのプラグインのことです。AnthropicがGitHubのIssueで「この機能をClaude Modsと呼ぶ」と表明していて、実装の呼び名である function hooks はそのまま残り、Modはそれを使うプラグインに過ぎない、という整理になっています。
ここで先に断っておくと、公式ドキュメントサイトにはまだModsのページがありません。読めるのは公式リポジトリの mods ディレクトリに置かれた4本のソースと、上のIssueだけです。
settings.json に hooks のキーを足し、.claude/agents を育て、気づけば CLAUDE.md も長くなっていく。そうやって設定ファイルを積み上げてきた人なら、また新しい名前を覚え直すのかと身構えませんか。でも中身を見ていくと、これは新技術というより .vimrc を削り続けてきた延長線にある話でした。設定ファイルで足りなくなったからプラグインを書く、という道を、僕らは何度か通ってきているんですよね。
hooksとModsは何が違うのか。
外部プロセスを起動していた従来のhooks
従来のhooksは、settings.json や hooks/hooks.json の hooks キーに「何をするか」を宣言する形でした。走らせられるのはシェルコマンド、HTTP POST、MCPツール、プロンプト、サブエージェントの5種類で、公式ドキュメントにも「マッチしたフックはすべて並列に走る」と書かれています。
並列に走るということは、順番も入れ子も作れないということです。前のフックの結果を受けて次が判断する、という書き方ができない。状態を持ちたければ、どこかのファイルに書いて次の起動で読み直すしかありませんでした。
TypeScriptの関数として組み込まれるMods
関数フックは、hooks/hooks.json の modules キーが指すTypeScriptモジュールを、Claude Code自身のプロセスの中で走らせます。別プロセスの起動を挟まないので、状態はモジュールのスコープにそのまま置けます。
合成の仕方も変わりました。公式の説明では、プラグインはミドルウェアのように入れ子になり、最初に登録されたものがいちばん外側を包みます。ExpressやKoaを触ったことがあるなら、あの感覚がそのまま持ち込まれていると思ってください。
実行時間の予算は1回のディスパッチあたり10秒ですが、この時計はフック自身のコードが動いている間しか進みません。next の呼び出しや $ 経由の呼び出しを待っている時間は勘定に入らない仕様になっていて、下流が遅くても自分の予算は減らないわけです。
そして、従来型のhooksは消えていません。classic.* というイベントとしてチェーンの中に残っているので、今動いているものを慌てて書き換える必要はないんですよね。仕組みの外側が分かったところで、次は実際に書くときの手触りを見ていきます。
register関数から見えてくる作りの発想
イベントを捕まえるという入り口
modの入り口は register(on, options) という関数ひとつです。on にイベント名とハンドラを渡して登録していきます。公式の型定義に載っている例が、いちばん短くて分かりやすい形でした。
on("tool.call", ($, e, next) => e.tool === "Bash" ? { deny: "no" } : next(e))Bashツールの呼び出しだったら { deny: "no" } で断り、それ以外は next(e) で先へ流す。これだけです。イベント名の指定は1個の名前のほかに、classic. のような名前空間まとめ、 の全部、!tool.describe のような否定も書けます。
次の処理に渡すnextという考え方
断ったあと、続きの処理はどこへ行くのでしょうか。答えは第3引数の next が持っています。ハンドラの引数 ($, e, next) は役割がはっきり分かれていて、$ はエンジンを操作するための入り口、e はそのイベントの引数、next はチェーンの残りです。
e は深いところまで凍結されていて、直接書き換えると型エラーになりますし実行時にも落ちます。改変したいときはコピーを作って next に渡す、という決まりになっています。
よくできているなと思うのは、$ の呼び出しそのものもイベントとして流れてくるところです。on('*') を1本置くだけで、他のプラグインが $.process.run や $.fs.write を呼んだことまで見える。監査ログが関数ひとつで書ける、というのが公式の言い分です。
Claude Modsはdotfiles文化の延長線にある
設定ファイルというのは、たいてい同じ道をたどります。最初はキーと値を並べるだけで足りていたのが、条件分岐を書きたくなり、状態を持ちたくなり、最後には「もう普通のコードで書かせてくれ」となる。Vimがvimrcからプラグインへ、エディタが設定画面から拡張機能へ進んだのも、同じ流れでした。
従来のhooksでできたのは「このイベントでこのコマンドを叩く」という宣言までで、少し込み入ったことをやろうとすると、シェルスクリプトの側がどんどん太っていきました。社内のClaude Codeに危険なコマンドのガードを足したときも、結局はJSONの外側にスクリプトが積み上がっていったんですよね。
関数フックはその太った部分を、型の付いたTypeScriptとして本体の中に置き直せるようにしたものだと捉えています。新しい概念を覚えるというより、置き場所が変わった、という感覚のほうが近いです。
Claude Modsは今すぐ触るべきか。
Early Accessという現在地
公式のREADMEには Early access と書かれていて、「modが書かれているAPIはリリース間で予告なく変わりうる」と明記されています。betaでもexperimentalでもなく、公式の言い方はこれです。
どのバージョンから使えるのかは、はっきりした記載が見つかりませんでした。Issueが触れているのは v267 / v268 という数字で、公開されている型定義は 2.1.277 が書き出したものです。2.1.267から268のあたりから触れるようになり、少なくとも2.1.277系では動く、くらいの理解が安全だと思います。
有効化の方法として公式が明示しているのは、コマンドの前に環境変数を置く形だけです。
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude公式のmodをソースから動かしてみたいなら claude --plugin-dir mods/diff で読み込めます。型定義は /plugin-types が書き出してくれるので手書きはしません。書いたものは claude plugin validate と claude plugin test で確かめられます。壊しても困らないところから試してみてください。
任意のコードが動くということ
modは任意のTypeScriptをClaude Code自身のプロセスの中で実行します。$.process.run でホストのコマンドが叩けて、$.fs.write でファイルが書けて、$.http.fetch で外に出られる。素性の分からないmodを入れるのは、任意コードの実行を許すのと同じ意味を持ちます。
Anthropic自身もこの力の大きさは自覚していて、組織の設定を守るための sec-default というmodを最外に座らせています。そのREADMEには、組織が今設定しているもののいくつかは関数フック以前には個人の手が届かない領域だった、という趣旨のことが書かれています。
入れていいmodかどうかは、何を見て判断すればいいのでしょうか。答えはシンプルで、claude plugin validate を通して、そのmodが何をフックして $ の何を呼ぶかを見ておくことです。この「足跡」を一覧にしているまとめもあります。
プロンプトの上に描画するタイプのmodを眺めると、雰囲気はつかみやすいです。
Claude Codeを手に馴染む道具にしていく
Claude Modsは、Claude Codeをカスタマイズする手段が、宣言から関数へ広がったという話です。従来のhooksが要らなくなったわけではないので、今の設定を捨てる判断は要りません。
仕事の本番環境にいきなり入れるものではないと思います。一方で、自分の手元で「この確認だけは毎回やってほしい」という1行を書いてみるのは、今日からできることです。
何年もかけて削ってきた設定の中で、いちばん手癖になっている1行はどれでしょうか。それを関数として書き直してみるところから始めるのが、この仕組みのいちばん分かりやすい入り口だと思います。



コメント
ログイン か 会員登録 するとコメントできます