Claude Code の Mod は、「フックが動く」ことと「描いたものが画面に出る」ことが別の話です。ターミナルでは両方動きますが、VS Code のチャットパネルでは、フックは動いても描いたものは出ません。この記事では、使う場所ごとに何が動くかを整理し、複数の場所で使われる Mod の書き方までまとめます。内容は Anthropic の公式ドキュメントと照合しています。
先に結論
- ペインや帯のような画面の描画が出るのは、ターミナルとデスクトップアプリ(WSL を除く)だけです。
- フックそのもの(ツール呼び出しの拒否やプロンプトの書き換え)は、VS Code、
claude -p、Agent SDK でも動きます。 - デスクトップアプリで WSL のセッションを使うと、プラグインが使えないため、Mod は動きません。
- 描画する Mod は、今どのアプリかを調べて、描けない場所では文字の返事に切り替えられます。
場所ごとの一覧
公式ドキュメントの表を、日本語にしたものです。
| 使う場所 | フックは動く | 描いたものは出る |
|---|---|---|
ターミナルの claude(エディタ内蔵のターミナル、JetBrains のプラグインを含む) | 動く | 出る |
| デスクトップアプリの Code タブ(WSL のセッションを除く) | 動く | 出る(ターミナル専用の要素を除く) |
| デスクトップアプリの WSL セッション | 動かない(プラグインが使えない) | 出ない |
| VS Code 拡張のチャットパネル | 動く | 出ない |
claude -p と Agent SDK | 動く | 出ない |
| claude.ai やモバイルアプリからの Remote Control | 手元のマシンのセッションで動く | 手元のマシンのターミナルに出る |
| クラウドセッション | プラグインがクラウドに引き継がれる場合は動く | 出ない |
Windows でデスクトップアプリを使っていて Mod が動かないときは、まずそのセッションが WSL かどうかを確かめてください。WSL のセッションでは、プラグインそのものが使えません。
ターミナルとデスクトップアプリの違い
どちらでもペイン、プロンプトの上の帯、スピナー、トランスクリプトの各行は描けます。ただし、使える部品(要素)と描ける場所に差があります。
| 要素・場所 | ターミナル | デスクトップアプリ |
|---|---|---|
Box、Text、Button、Link、Code、Markdown、Input、Select、Client | 使える | 使える |
Svg(SVG の図) | 使えない | 使える |
Raster(色つきのマス目)、Image(画像) | 使える | 使えない |
ToolProgress、TurnDuration、InfoNotice(状態の行) | 描ける | 描けない |
デスクトップアプリで描けない要素を使うと、描画が出ません。「ターミナルでは動くのにデスクトップアプリでは出ない」ときは、まずこの表を見てください。公式のリファレンスにある レンダーサイトと要素の表 が正式な一覧です。
どこでも動くように書く
描画のフックは、e.surface(terminal か desktop)で今のアプリを知れます。そして $.ui.resolve(e) を使うと、今のアプリで使える要素だけが返ります。次の Mod は、プロンプトの上の帯に、今のアプリの名前を 1 行足します。コードは Claude Code 2.1.289 の claude plugin validate と claude plugin test で確認しました。
export function register(on) {
// プロンプトの上の帯。ターミナルとデスクトップアプリの両方で描かれる
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
// 今のアプリで使える要素だけが入っている
const { Box, Text } = $.ui.resolve(e)
const where = e.surface === 'desktop' ? 'デスクトップアプリ' : 'ターミナル'
// 他の Mod が帯に描いたものを残して、その下に 1 行足す
const theirs = await next(e)
return Box({
flexDirection: 'column',
children: [theirs, Text({ dimColor: true, children: ['ここは ' + where + ' です'] })],
})
})
}ポイントは 3 つです。
next(e)の結果を残す:帯はすべての Mod で共有しています。await next(e)の結果を自分の木の中に入れると、ほかの Mod の表示を消さずに済みます。入れずに木を返すと、後ろの Mod の描いたものを置き換えます。- 要素は
$.ui.resolve(e)から取る:直接決め打ちせず、今のアプリが描ける要素を受け取ります。 - 描けない場所の逃げ道を用意する:描画がないアプリ(VS Code、
claude -p)でも情報を出したいときは、/コマンドの文字の返事({ text })や、$.ui.logでトランスクリプトに出す 1 行を併用します。
claude -p と CI での注意
claude -p(対話なしの実行)でもフックは動きます。ただし、次の違いがあります。
--plugin-dirを付けた実行では、Mod が読み込まれなかったときのメッセージは、トランスクリプトがないため標準エラー出力に出ます。ほかの Mod による拒否は、デバッグログにだけ残ります。/pluginは動きません。インストール済みのプラグインは読み込まれるので、管理はシェルのclaude pluginコマンドで行います。$.ui.askのように、ユーザーに聞く呼び出しは、答える人がいないため失敗します。tryとcatchで受けて、安全な側(拒否)に倒す書き方が公式の例にもあります。- Claude が書いた Mod の自動読み込み(ホットリロードの承認)は、承認する人がいないため、
claude -pやdontAskモードでは読み込まれません。
CI のように、フラグを渡せない場所では、環境変数 CLAUDE_CODE_PLUGIN_DIRS で、--plugin-dir と同じようにプラグインのフォルダを渡せます。Windows ではパスを ; で区切ります。
確認の仕方
- 動いているか:セッションで
/pluginを実行し、1 mod active · 名前の行を見ます。VS Code やclaude -pでは見られないので、claude plugin listを使います。 - ターミナルとデスクトップアプリの両方での描画:Mod のテストで、
$.ui.mountのsurfaceにterminalとdesktopを順に渡すと、描画が各アプリで有効かを調べられます。ただし、実際にどう見えるかまでは確認できないので、新しい配置は実機でも見てください(Mod のテストの書き方)。
まとめ
Mod を配るなら、README に「どのアプリで動くか」を書くと親切です。描画がターミナル専用なのか、フックだけで足りるのかを、利用者は知りたがります。描画の作り方は 画面に状態を表示する Mod に、原因の切り分けは Mod が動かないときの確認手順 にあります。