はじめに
Claude Code を毎日使っていると、「ここにこれが表示されていたら便利なのに」「このコマンドだけは実行前に一度止まってほしい」といった、小さいけれど自分にとっては切実な要望が出てきます。ところが、こうした要望の多くは「多くの人に必要とは言えない」ため、公式機能として実装される見込みは高くありません。
Claude Code 2.1.287 で正式に使えるようになった mods(モッド) は、この問題に正面から答える機能です。mod は Claude Code の中で動く小さな関数で、画面に枠やボタンを描いたり、ツール呼び出しを止めたり書き換えたり、自分専用のコマンドを足したりできます。しかも、コードを自分で書く必要すらありません。Claude に「こういう mod を作って」と頼めば、その場で書いて読み込んでくれます。
図1:mods の前と後。左の「これまで」は外から動く仕組みしかなく、右の mods では Claude に頼む→承認する→その場で反映、の 4 ステップで機能が増えます。
この記事では、公式ドキュメントで mods の仕組みを確認したうえで、筆者の Mac で次の 3 通りの方法で実際に mod を動かし、その画面と録画を載せています。
-
公式サンプルを入れる:危険な
rm -rfを止めて、消えるファイルを一覧表示する mod など -
自分で書く:ツールの使用回数をプロンプトの上に表示し、
.envの編集を止める mod - Claude に作らせる:「git ブランチ名を表示する mod を作って」と日本語で頼む
想定読者は、Claude Code を日常的に使っていて、設定フック(hooks)やスキルを少し触ったことがある方です。JavaScript を書いたことがなくても、3 つ目の「Claude に作らせる」方法なら試せます。
この記事の確認範囲
| 項目 | 内容 |
|---|---|
| 確認日 | 2026 年 10 月 3 日 |
| 環境 | macOS(Apple Silicon)、Claude Max プラン |
| Claude Code | CLI 2.1.288(検証前は 2.1.280 だったため更新) |
| 実行モデル | 録画は応答を速くするため Sonnet 5.5 を指定(--model sonnet) |
| 情報源 | Claude Code 公式ドキュメントの Mods の章(10 ページ)と、公式サンプル集 claude-code-playground
|
| 未確認 | デスクトップアプリ(Code タブ)での動作、組織向けの管理設定、組み込み mod「You should know」 |
デスクトップアプリの Code タブでも mods は動くと公式に書かれていますが、筆者の PC のアプリに同梱されていた Claude Code は 2.1.286 で、条件の 2.1.287 に届いていなかったため、今回は CLI だけで検証しました。
mods とは何か
一言でいうと「Claude Code の中で呼ばれる関数」
公式ドキュメントは mod を「Claude Code の見た目と振る舞いを変えるプラグイン」と説明しています。中身は JavaScript か TypeScript で書いたイベントハンドラ(ある出来事が起きたときに呼ばれる関数)です。Claude がツールを使おうとした、プロンプトが送信された、画面のある部分が描かれようとしている、といったイベントのたびに Claude Code があなたの関数を呼び、関数はそのイベントを眺めるだけにすることも、内容を変えることも、丸ごと引き受けることもできます。
ここで混乱しやすいのが「フック(hook)」という言葉です。Claude Code には以前から、settings.json に書いたシェルコマンドをイベント時に実行する 設定フック(settings hook) がありました。mods のドキュメントでは、mod の中の関数のほうを「フック」と呼び、従来のものは「設定フック」と呼び分けています。この記事もその呼び方に合わせます。
ほかの拡張方法との違い
Claude Code の拡張方法はすでにいくつもあります。違いを一枚にまとめると次のようになります。
図2:mod だけが Claude Code の「中」で関数として呼ばれます。外側の 4 つはスクリプトの出力やテキスト、道具を渡す仕組みです。
公式の比較表を、選び方の観点で書き直したのが次の表です。
| mod | 設定フック | スキル | MCP サーバー | |
|---|---|---|---|---|
| 正体 | プラグイン内の関数(本体のプロセス内で実行) | イベント時に動くシェル・HTTP・プロンプト | Claude が読む SKILL.md
|
道具を提供する別プロセス |
| 変えられるもの | ツール呼び出し・プロンプト・コマンド・ターン・画面の描画 | ツール呼び出しやプロンプトの可否、引数と結果、追加の文脈 | Claude の知識と行動 | Claude が使える道具 |
| 画面に描けるか | 描ける | 描けない | 描けない | 描けない |
| 書くもの | JavaScript / TypeScript | スクリプト+settings.json
|
Markdown | 任意の言語のサーバー |
| 向いている場面 | ペイン・帯・独自コマンドが欲しい、イベントを書き換えたい | 手持ちのスクリプトで許可・拒否・記録したい | 毎回同じ指示を貼っている | 外部システムにつなぎたい |
ポイントは「画面に描けるか」の行です。ステータスラインも文字は出せますが 1 行のテキストだけです。mod はボタン付きのペインを開いたり、本体が描いているスピナーやツールの行を差し替えたりできます。逆に、すでに設定フックやスキルで足りているなら、無理に mod にする必要はありません。プラグインは mod・スキル・MCP サーバーを同梱できるので、組み合わせて配ることもできます。
組み込みの mod もある
実は Claude Code 自身の機能の一部も mod として作られています。/plugin の Installed タブには「Built-in」として、AGENTS.md を読み込む cc-plugin-agents-md、/diff の画面を描く cc-plugin-diff、mod を書くためのスキルを持つ cc-plugin-plugin-authoring などが並びます(後半の実画面で確認できます)。このうち一部はソースコードが公開されていて、自分で mod を書くときの良い手本になります。
ニュースレターで紹介された「You should know」も組み込み mod の一つです。作業中に横で別のエージェントが様子を見て、見落としがありそうなときにプロンプトの上へ注意書きを出してくれます。初期状態では無効で、/plugin enable cc-plugin-you-should-know@builtin で有効にできると書かれていますが、筆者の環境の一覧には表示されておらず、今回は試していません。
mod の仕組み
フックは 3 通りに振る舞える
mod のフックは、どのイベントでも同じ 3 つの引数を受け取ります。$ は mods API(画面を描く、コマンドを足す、ファイルを読むといった外の世界への窓口)、e はイベントの中身、next は「次へ渡す」関数です。この next を呼ぶかどうかで、フックの振る舞いが 3 通りに分かれます。
図3:next(e) をそのまま呼べば「見るだけ」、中身を変えて呼べば「書き換え」、呼ばずに結果を返せば「代わりに答える」になります。
特に覚えておきたいのは 3 つ目です。たとえば Bash の呼び出しに対して { deny: '理由' } を返すと、コマンドは実行されず、権限確認の画面も出ません。Claude はその理由をツールの結果として読むので、理由の文は「Claude が次にどうすべきか分かる指示」として書くのがコツです。今回の自作 mod でも、この書き方の効果がはっきり出ました(後述)。
ファイルは 3 つだけ
mod の正体は普通のプラグインです。最小構成はファイル 3 つで、hooks/hooks.json に modules というキーがあることが「このプラグインは mod である」という目印になります。
図4:plugin.json が名札、hooks.json がコードの場所、register.js が本体です。.js も .ts もビルドなしでそのまま読み込まれます。
Node.js やバンドラー(複数ファイルを 1 つにまとめる道具)は要りません。Claude Code が .js / .ts / .tsx を直接読み込みます。また、--plugin-dir で読み込んだ mod のフォルダには、Claude Code が自分のバージョンに合った型定義(.d.ts)を書き出してくれるので、エディタの補完も効きます。公式ドキュメントは「ページの説明と型定義が食い違ったら型定義を信じて」と明言しています。バージョンごとに API が変わる可能性があるためです。
描ける場所
画面に描くには ui.render というイベントにフックを登録し、component で描く場所を選びます。代表的な場所は次の図のとおりです。
図5:新しく描ける場所(Pane・AbovePrompt)と、本体が描いている部分を差し替えられる場所(ToolUse・Spinner など)があります。権限確認の画面だけは変えられません。
-
Pane(横のペイン):
$.ui.openで開く領域です。幅の広い端末では会話の横に、狭い端末ではプロンプトの上の枠として表示されます。 - AbovePrompt(プロンプト上の帯):プロンプト入力欄のすぐ上の 1 行です。すべての mod で共有します。
- 本体が描く部分:メッセージ、ツールの行、スピナー、質問ダイアログ、フッターの表示なども差し替えや装飾ができます。
mods が動く場所・動かない場所
フックそのものはほとんどの実行形態で動きますが、「描いたものが表示されるか」は場所によって違います。
| 実行のしかた | フックが動く | 描いたものが見える |
|---|---|---|
ターミナルの claude(エディタ内蔵ターミナル、JetBrains 含む) |
はい | はい |
| デスクトップアプリの Code タブ(WSL セッション以外) | はい | はい(ターミナル専用の要素を除く) |
| VS Code 拡張のチャットパネル | はい | いいえ |
claude -p、Agent SDK |
はい | いいえ |
| Remote Control(claude.ai やスマホから操作) | はい(手元のセッションで) | 手元のターミナルで |
| クラウドセッション | 条件付きではい | いいえ |
claude -p で動かす自動処理でもフックは働くので、「描画は要らないがツール呼び出しを止めたい」という用途なら、ヘッドレス実行にも使えます。
準備:バージョンを確認する
mods には Claude Code 2.1.287 以上が必要です。筆者の CLI は 2.1.280 だったので、まず更新しました。
# 今のバージョンを確認する
claude --version
# 2.1.287 より古ければ更新する(ネイティブ版インストールの場合)
claude update
筆者の環境では Successfully updated from 2.1.280 to version 2.1.288 と表示され、2.1.288 になりました。mods は 2.1.287 以降では既定で有効になっているので、追加の設定は要りません。早期アクセス期間に CLAUDE_CODE_ENABLE_FUNCTION_HOOKS という環境変数を設定していた方は、もう意味がないので削除してください。
公式サンプルを使う場合は、サンプル集のリポジトリを取得しておきます。
# 公式サンプル mod を取得する
git clone https://github.com/anthropics/claude-code-playground.git
cd claude-code-playground/claude-code/mods
ls # blast-radius replay-theater token-weather
実践 1:公式サンプル mod を動かす
最初は、Anthropic が公開しているサンプルを入れてみます。サンプルは 3 つあります。
| サンプル | できること | 使っているイベント |
|---|---|---|
token-weather |
コンテキストの使用率を天気予報のように帯で表示 |
turn.complete、ui.render(AbovePrompt) |
blast-radius |
rm -rf や force push などを止め、影響範囲を見せて実行するか選ばせる |
tool.call(Bash)、ui.render(Pane) |
replay-theater |
/replay で直前のターンのファイル編集を 1 つずつ見返す |
command.run、tool.call など |
入れる前に中身を確かめる
mod はあなたの権限で動くコードなので、入れる前に何をするのか確かめるのが基本です。claude plugin validate を使うと、コードを実行せずに「どのイベントを扱い、どの API を呼ぶか」が分かります。
claude plugin validate ./blast-radius
筆者の環境での出力(要点)は次のとおりでした。
❯ ./blast-radius.mjs hooks: tool.call{tool=Bash}, ui.render{component=Pane}, ui.render{component=AbovePrompt}
❯ ./blast-radius.mjs calls: $.clock.now, $.process.run, $.session.cwd, $.ui.close, $.ui.invalidate, $.ui.open, $.ui.resolve, $.ui.toast
✔ Validation passed
Bash のツール呼び出しを扱い、$.process.run(プロセスの起動)で影響範囲を調べ、ペインを開く、という構成が読み取れます。ネットワークを使う $.http.fetch が含まれていないことも、ここで確認できます。
2 つを同時に読み込んで試す
--plugin-dir を使うと、インストールせずにそのセッションだけ mod を読み込めます。複数指定もできるので、token-weather と blast-radius を一緒に読み込みました。検証用の小さなプロジェクト(build/ フォルダに 6 ファイル)で、Claude に削除を頼みます。
# 検証用プロジェクトのフォルダで実行する
M=<クローンした場所>/claude-code-playground/claude-code/mods
claude --plugin-dir $M/token-weather --plugin-dir $M/blast-radius
プロンプトには「build フォルダはもう要らないので rm -rf build で消して」と入力しました。Claude が ls -la build && rm -rf build && echo "deleted" を実行しようとした瞬間、右側にペインが開いて処理が止まりました。
実画面:rm -rf build の実行前に、消える 6 ファイル(約 104 KB)の一覧と Proceed / Cancel が表示されました。カーソルは最初から Cancel にあるため、うっかり Enter を押しても消えません。
ここで 2(Cancel)を押すと、Claude は「実行前の確認画面でキャンセルされたため、コマンドは動いていません(削除対象は 6 ファイル、約 104KB でした)」と答えました。続けて「確認したので大丈夫です。もう一度 rm -rf build を実行して」と頼み、今度は 1(Proceed)を押すと削除が実行されました。
実画面:上の枠がキャンセル時の Claude の説明、中央が 2 回目の削除完了、下の枠が token-weather の帯です。Sonnet 5.5(100 万トークン)で 4% 使用と表示されています。
一連の流れを 2 倍速で録画したのが次の GIF です。
録画(2 倍速、約 21 秒):止まる→一覧を見る→キャンセル→再依頼→実行、の順に進みます。
ふだんの権限確認でも rm -rf build の文字列は見えますが、「実際に何が消えるのか」までは表示されません。blast-radius は find や du を実行して中身を数えてから見せてくれるので、判断材料が増えます。権限確認の画面そのものは mod から変えられない仕様のため、このように「権限確認の前に自前の確認を挟む」のが mod らしい作り方です。
使い続けるならマーケットプレイスとして登録する
--plugin-dir はその場限りです。毎回読み込みたい場合は、クローンしたフォルダをローカルのマーケットプレイスとして登録し、インストールします(今回はインストールまではしていません)。
# claude-code-playground/claude-code/mods で実行する
claude plugin marketplace add ./
claude plugin install token-weather@claude-code-playground-mods --scope user
マーケットプレイスはクローンしたフォルダを指すので、フォルダを移動・削除すると mod も読み込まれなくなる点に注意してください。
実践 2:自分で mod を書く
次は自分で書いてみます。公式チュートリアルの「ツール呼び出しを数える mod」を参考に、日本語で使いやすいように作り変えた tool-meter を作りました。機能は 3 つです。
- プロンプトの上の帯に「このターンで使ったツールの回数(種類別)と経過秒数」を表示する
-
/meterコマンドで帯の表示・非表示を切り替える -
.envや.env.localなどの秘密情報ファイルへの Edit / Write を止める
ファイルを作る
フォルダとマニフェスト(名札)、hooks.json を作ります。
mkdir -p tool-meter/.claude-plugin tool-meter/hooks tool-meter/tests
{
"name": "tool-meter",
"version": "0.1.0",
"description": "このターンで Claude が使ったツールの回数をプロンプトの上に表示し、.env の編集を止める",
"author": { "name": "<YOUR_NAME>" }
}
上を tool-meter/.claude-plugin/plugin.json として保存します。次の内容を tool-meter/hooks/hooks.json として保存します。
{
"description": "tool-meter の hooks モジュール",
"modules": ["./register.js"]
}
本体(hooks モジュール)を書く
本体の tool-meter/hooks/register.js です。ファイルの先頭で宣言した変数(counts など)を複数のフックで共有するのが mod の基本形です。
// このターンで使ったツールの回数(ツール名 → 回数)
let counts = {}
// ターンが始まった時刻(ミリ秒)と、最後に測った経過秒数
let startedAt = 0
let seconds = 0
// バンドを表示するかどうか(/meter で切り替える)
let visible = true
// .env / .env.local などを守る
const PROTECTED = /(^|\/)\.env(\.|$)/
export function register(on) {
// セッション開始時(と再読み込み時)に /meter コマンドを追加する
on('session.start', async ($, e, next) => {
await $.command.register({ name: 'meter', description: 'ツール回数バンドの表示を切り替える' })
return next(e)
})
// プロンプトを送ったら、集計をリセットする
on('prompt.submit', async ($, e, next) => {
counts = {}
seconds = 0
startedAt = await $.clock.now()
$.ui.invalidate('ui.render')
return next(e)
})
// ツールが呼ばれるたびに数え、.env の編集だけは止める
on('tool.call', async ($, e, next) => {
if ((e.tool === 'Edit' || e.tool === 'Write') && PROTECTED.test(e.file_path)) {
$.ui.toast('tool-meter: ' + e.file_path + ' の編集を止めました')
return { deny: e.file_path + ' は秘密情報のファイルなので編集できません。値はユーザーに設定してもらうよう伝えてください。' }
}
counts[e.tool] = (counts[e.tool] || 0) + 1
seconds = Math.round(((await $.clock.now()) - startedAt) / 1000)
$.ui.invalidate('ui.render')
return next(e)
})
// ターンが終わったら最終の経過秒数を記録する
on('turn.complete', async ($, e, next) => {
seconds = Math.round(((await $.clock.now()) - startedAt) / 1000)
$.ui.invalidate('ui.render')
return next(e)
})
// /meter を打ったら表示を切り替える
on('command.run', { command: 'meter' }, async ($) => {
visible = !visible
$.ui.invalidate('ui.render')
return { text: visible ? 'ツール回数バンドを表示します' : 'ツール回数バンドを隠しました' }
})
// プロンプトの上(AbovePrompt)に 1 行描く
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
const names = Object.keys(counts)
if (!visible || names.length === 0) return next(e)
const { Box, Text } = $.ui.resolve(e)
const total = names.reduce((sum, n) => sum + counts[n], 0)
const detail = names.map((n) => n + ' ' + counts[n]).join(' · ')
return Box({
children: [
Text({ color: 'cyan', bold: true, children: ['▶ このターンのツール ' + total + '回 '] }),
Text({ children: [detail] }),
Text({ dimColor: true, children: [' | ' + seconds + '秒'] }),
],
})
})
}
書くときに押さえておきたい点を補足します。
-
$.ui.invalidate('ui.render'):数が変わっても、Claude Code に「描き直して」と伝えないと画面は更新されません。値を変えた直後に呼びます。 -
$.ui.resolve(e):描画に使う部品(Box、Textなど)は、今描いている画面(ターミナルかデスクトップか)に合わせてここから取り出します。 -
childrenは配列:最初に文字列をそのまま渡して書いたところ、公式の例はすべて配列で渡していたため合わせました。 -
$はそのまま書く:const ui = $.uiのように変数へ入れると、静的解析で$.ui is used as a valueというエラーになります。Claude Code が「この mod が何を呼ぶか」を事前に列挙するための制約です。
検証とテストを実行する
書いたら validate で構造を確認し、claude plugin test で自動テストを流します。テストは tests/tool-meter.test.ts に書きました。ツールの実行はテストが代わりに答えるので、Claude へのリクエストもネットワークも使いません。
import { expect, test } from 'claude-code/testing'
test('.env の編集は止め、ほかのツールは数える', async ($, on) => {
// ツールの実行は Claude Code の代わりにテストが答える
on('tool.call', () => ({ result: 'ok' }))
// .env.local への Edit は deny で返る
const blocked = await $.tool.call({ tool: 'Edit', file_path: '.env.local', old_string: 'A', new_string: 'B' })
expect(blocked.deny).toContain('.env.local')
// 通常のファイルは通る
const ok = await $.tool.call({ tool: 'Read', file_path: 'README.md' })
expect(ok.result).toBe('ok')
})
test('/meter で表示を切り替える', async ($) => {
const first = await $.command.run({ command: 'meter', args: '' })
expect(first.text).toBe('ツール回数バンドを隠しました')
const second = await $.command.run({ command: 'meter', args: '' })
expect(second.text).toBe('ツール回数バンドを表示します')
})
claude plugin validate ./tool-meter
cd tool-meter && claude plugin test
実画面:上の枠が validate が見つけたイベントと API の一覧、下の枠がテスト 2 件の合格です。テストは 0.13 秒で終わりました。
実は、更新した直後に初めて claude plugin test を実行したときは、次のエラーで動きませんでした。
claude plugin test: hooks modules are turned off in this process: the rollout switch was saved off by an earlier session and is not refreshed yet. Start `claude` once with network access, then run the tests again
古いバージョンのときに保存された「mods は無効」という配信設定が残っていたのが原因です。メッセージのとおり、ネットにつながった状態で一度 claude を起動(筆者は claude -p で短い質問を 1 回)してから再実行すると、問題なく通りました。更新直後に同じ表示が出た方は試してみてください。
実際に使ってみる
検証用プロジェクトで tool-meter を読み込み、「src のファイルを読んで、cart.js の合計に送料 500 円を足して。ついでに .env.local の API_KEY を TEST に書き換えて」と、わざと秘密情報ファイルの編集も混ぜて頼みました。
claude --plugin-dir ../mods/tool-meter
実画面:右上の枠が mod の出したトースト(短い通知)、左中央が止められた .env.local の編集、下の枠が作業中に増えていくツール回数の帯です。
cart.js の編集は通常どおり行われ、.env.local への Edit は mod が止めたため「Error editing file」になりました。興味深かったのは Claude の最終回答です。deny の理由に「値はユーザーに設定してもらうよう伝えてください」と書いておいたところ、Claude は Bash で回り込んで書き換えようとはせず、「編集ツールが『秘密情報のファイルなので編集できません』と拒否しました。Bash の sed などで回避することはしていません」と説明し、自分で実行するためのコマンドを提示して終わりました。deny の文章がそのまま Claude への指示として効くことが確認できます。
なお、この mod は Edit / Write だけを見ているので、Bash の sed で書き換える経路は止めていません。本気で守るなら、設定の permissions.deny やサンドボックスと組み合わせるべきです。mod は「うっかり」を防ぐ層として考えるのが安全です。
続いて /meter を 2 回打つと帯が隠れて、また表示されました。コマンドの返事は「tool-meter: ツール回数バンドを隠しました」のように、先頭にプラグイン名が自動で付きます。
セッションを開いたまま書き換える(ホットリロード)
--plugin-dir で読み込んだフォルダは Claude Code が監視していて、ファイルを保存すると自動で再読み込みされます。セッションを開いたまま、帯の見出しを「▶ このターンのツール」から「■ ツール使用」に書き換えて保存しました。
実画面:上の枠が再読み込みの通知(6 つのフックを読み込み直した)、下の枠が新しい見出しで描かれた帯です。
会話に tool-meter: reloaded (6 hooks: session.start, prompt.submit, tool.call, turn.complete, command.run, ui.render) と表示され、次の質問から新しい見出しになりました。Claude Code を再起動する必要はありません。ただし再読み込みは register をもう一度実行するのと同じなので、ファイル先頭の変数(今回の counts)はリセットされます。値を残したい場合は、セッションの間だけ保持する $.state や、セッションをまたいで保存する $.store を使います。
ここまでの操作を 1.5 倍速で録画しました。
録画(1.5 倍速、約 16 秒):帯の数字が Bash 1 → Read 3 → Edit 1 と増え、.env.local で止まる様子が見えます。
実践 3:Claude に mod を作らせる
いよいよ本命の「頼むだけで作ってもらう」方法です。この方法では、Claude Code に組み込まれた plugin-authoring スキルが使われます。今動いているバージョンの型定義やサンプルを Claude が読んでから書くので、バージョン違いによる書き間違いが起きにくい仕組みです。
図6:筆者が実際に試したときの順番です。人がするのは「頼む」と「承認する」の 2 つだけでした。
頼んだ内容
--plugin-dir なしで普通に claude を起動し、次のように頼みました。
プロンプトの上に、今の git ブランチ名と、まだコミットしていないファイルの数を表示する mod を作って。/gitbar で表示を切り替えられるようにして
Claude はまず Skill(plugin-authoring) を読み込み、型定義とサンプルを調べてから、~/.claude/dev-mods/<セッション ID>/gitbar/ にファイルを書き始めました。最初のファイルが保存された時点で、次の確認が表示されます。
実画面:「このセッションでホットリロードを有効にしますか?」の確認です。この質問に答えられるのは人だけで、権限モードや設定では代わりに答えられません。
選択肢の意味は次のとおりです。
| 選択肢 | 何が起きるか |
|---|---|
| How does this work? | 説明が表示され、同じ質問に戻る |
| Enable for this session | ターン終了時に mod を読み込み、以降も変更のたびに再読み込みする(セッションを再開しても有効) |
| Not now | 今は読み込まない。ファイルは残り、次にそのセッションを開いたときに読み込まれる |
この確認は作業を止めません。Claude は書き続け、途中で validate に「$ を渡す関数はファイル先頭の関数宣言にする」と指摘されたのを自分で直していました。Enable for this session を選ぶと、ターンの終わりに「gitbar: loaded from this session's mods folder」と表示され、プロンプトの上に帯が出ました。
実画面:下の枠が読み込みの通知と、Claude が作った帯です。cart.js が未コミットだったため「1 uncommitted」と表示されています。
頼んでから使えるようになるまで、約 50 秒でした。/gitbar を打つと表示が消え、もう一度打つと戻ることも確認しました。Claude の報告には「validate は通っている」「テストは書いていないので実行しておらず、画面での表示も確認していない」と、確認していないことまで正直に書かれていたのも好印象です。
録画(2.5 倍速、約 29 秒):頼む→スキルを読む→ファイルを書く→承認→読み込み→/gitbar、の流れです。
Claude が書いたコードの特徴
生成された register.tsx(約 100 行)を読むと、自分で書いた tool-meter との違いが分かりました。
-
JSX で描いている:
<Box><Text dimColor>branch </Text>…</Box>のように HTML に近い書き方です。.tsxならこう書けます。 -
$.stateを使っている:表示状態とブランチ情報をatom/read/updateで持っているので、再読み込みしても値が消えません。 -
更新のタイミングを絞っている:
git status --porcelain -bを 1 回だけ実行し、セッション開始・プロンプト送信・ターン終了と、読み取り専用ではないツールの実行後だけ更新します。
一方で、/gitbar の返事が「gitbar: gitbar: off」と二重になっていました。Claude Code がコマンドの返事の先頭にプラグイン名を自動で付けるのに、mod 側でも「gitbar:」を付けていたためです。気になる場合は「返事の先頭の gitbar: を外して」と頼めば、次のターンの終わりに再読み込みされて直ります。
作った mod を残す
Claude に作らせた mod は、作ったセッションでしか読み込まれません。さらに ~/.claude/dev-mods/ のフォルダは、設定の cleanupPeriodDays(会話記録の保存日数)を過ぎると自動で削除されます。気に入った mod は自分の場所へコピーしておきましょう。
# セッションIDのフォルダから、自分の mods フォルダへコピーする
mkdir -p ~/mods
cp -R ~/.claude/dev-mods/<SESSION_ID>/gitbar ~/mods/gitbar
# 次回からはこう読み込む
claude --plugin-dir ~/mods/gitbar
毎回 --plugin-dir を付けるのが面倒な場合や、フラグを渡せないアプリから使う場合は、環境変数 CLAUDE_CODE_PLUGIN_DIRS に絶対パスを : 区切りで書く方法もあります。チームに配るなら、プラグインと同じくマーケットプレイスに載せるのが正攻法です。
読み込まれている mod を確認する
どの mod が読み込まれているかは、セッション内で /plugin を開くと分かります。
実画面:上の枠が「読み込まれた mod の数と名前」、中央が --plugin-dir で読み込んだ tool-meter(inline)、下の枠が組み込み mod です。組み込み mod は「mod active」の数には含まれません。
タブの下の薄い行に 1 mod active · tool-meter と出ていれば読み込み成功です。組み込み mod の cc-plugin-sec-default(組織の管理対象を守る仕組み)は、管理設定のある PC や Team / Enterprise プランでだけ読み込まれるため、個人の Max プランの筆者の一覧には出ていませんでした。
安全に使うために
mods の強さは、そのまま注意点でもあります。mod はサンドボックス(隔離された実行環境)の外で、あなたと同じ権限で動きます。
図7:左が「mod にできること」、右が「止め方」です。上から順に、影響範囲の小さい対策になっています。
公式ドキュメントが挙げている「読み込まれた mod ができること」は次のとおりです。
- ファイルの読み書き、プログラムの起動、ネットワーク通信をあなたとして行う
- 環境変数や設定ファイルに置いた API キーなどの秘密を読む
- 送ったプロンプトと、Claude のすべてのツール呼び出しを見る
- プロンプトやツール呼び出しを書き換える、あなたが打ったかのようにプロンプトを送る
- 権限確認を出す前に、ツール呼び出しを許可してしまう
- あなたのプランや API キーでモデルを呼び出す
サンドボックスを有効にしていても、守られるのは Claude が実行する Bash コマンドで、mod が起動したプロセスは対象外です。このため、入れる mod は信頼できる作者・マーケットプレイスのものに限り、入れる前に claude plugin validate の hooks: と calls: の行を確認する習慣をつけましょう。$.http.fetch(通信)や $.process.run(プロセス起動)、$.model(モデル呼び出し)が並んでいたら、その理由をコードで確かめる価値があります。
止め方は影響範囲ごとに用意されています。
| やりたいこと | 方法 |
|---|---|
| 特定の mod だけ止める |
/plugin の Installed タブで無効化・アンインストール |
| このセッションだけ全部止める |
claude --safe-mode で起動(ほかのカスタマイズも止まる) |
| 自分が入れた mod を常に止める |
~/.claude/settings.json に "disableAllHooks": true(設定フックとステータスラインも止まる) |
| 組織で制限する | 管理設定の allowManagedModsOnly などで、組織の mod だけを許可する |
なお、これらの方法で止まるのは自分で入れた mod で、組み込み mod は止まりません。組み込み mod を止めたい場合は、/plugin から個別に無効化します。
トラブルシューティング
mod が何もしないときは、次の順番で確認すると原因に早くたどり着けます。
図8:上から順に確認します。★は今回の検証で実際に当たったものです。
| 症状・メッセージ | 原因 | 対処 |
|---|---|---|
| mod の機能が何も出てこない | バージョンが 2.1.287 未満 |
claude --version を確認し、claude update で更新する |
/plugin の「mod active」行に名前がない |
読み込みに失敗している、またはフォルダを間違えている |
--plugin-dir のパスと、hooks/hooks.json の modules を確認する |
"tool.calls" is not an event |
イベント名の綴り間違い |
claude plugin validate でエラー箇所を確認する |
$.ui is used as a value |
$ やその一部を変数に入れている |
$.ui.open(...) のように毎回そのまま書く |
the rollout switch was saved off ... |
更新前の「無効」設定が残っている | ネットに接続した状態で claude を一度起動してから再実行する |
| ペインが開かない | 頼まれずに開くペインは端末幅 144 列以上が必要 | 端末を広げる。コマンドやボタンから開けば幅は問わない |
| 確認を待っている間に処理が進んでしまう | フック自身の処理時間が 10 秒を超えて打ち切られた | 待つ処理は $.ui.ask など mods API の呼び出しの中で行う(API 内の待ち時間は数えない) |
| 編集が反映されない | インストール済みのコピーはバージョン単位でキャッシュされる | 開発中は --plugin-dir でフォルダを直接読み込む |
原因が分からないときは claude --debug で起動するとデバッグログに、フックが失敗した理由や、描画内容が受け付けられなかった理由が 1 行ずつ記録されます。
まとめ
mods は、Claude Code を「自分専用の道具」に近づける拡張です。今回の検証で分かったことを振り返ります。
- mod は Claude Code の中で呼ばれる関数で、ツール呼び出し・プロンプト・画面の描画に対して「見る/書き換える/代わりに答える」ができる
-
ファイル 3 つ、ビルド不要。
--plugin-dirで読み込めば、保存するたびにその場で再読み込みされる -
公式サンプルの blast-radius は、
rm -rfの前に消えるファイルを数えて見せてくれた。権限確認の画面は変えずに、その手前に確認を足すのが mod らしい使い方 - 自作の tool-meter では、deny の理由文が Claude への指示として効き、Claude は回り込まずにユーザーへ作業を返した
- Claude に頼むだけで、約 50 秒で git ブランチを表示する mod ができた。人がしたのは依頼と「Enable for this session」の選択だけ
-
あなたの権限で、隔離なしで動くので、入れる前に
claude plugin validateで中身を確認する
次の一歩としては、普段「毎回確認していること」や「何度も打っているコマンド」を一つ選び、Claude に mod にしてもらうのがおすすめです。慣れてきたら、公式の組み込み mod(/diff や sec-default)のソースを読むと、ペインのボタン操作や方針を強制する mod の作り方が分かります。claude plugin test でテストを書いておけば、Claude Code の更新で API が変わったときにも気づけます。



















