2026年10月5日時点で、Claude Code Mods(モッズ)は、Claude Codeの出来事(イベント)にJavaScriptかTypeScriptの関数を登録して、動作と画面を変える仕組みです。Claude Code 2.1.287(2026年10月1日)で正式に加わり、既定でオンになっています。modはプラグインの一種で、必須のファイルは plugin.json、hooks.json、フックを書くコード本体の3つだけです。フックは ($, e, next) の3つの引数を受け取り、出来事を観て通す、書き換えて通す、自分で答えて止める、のどれかを選びます。この記事は開発者向けに、最小構成、フックの書き方、イベントとmods API、コマンドとツールの追加、ペインの描画、状態の置き場所、テストとデバッグ、共有までを、公式ドキュメントの例に沿って整理します(Mods overview)。
この記事の要点(2026年10月5日時点)
- modはプラグイン。hooks/hooks.json に modules を書くと、そのプラグインはmodになる。コード本体は register(on, options) を書き出すES module
- フックは on(event, matcher, hook) で登録する。引数は $(mods API)、e(イベントの入力)、next(次のハンドラ)の3つ
- 外に出る操作(描画、コマンド、モデル呼び出し、ファイル、プロセス、ネットワーク)はすべて $ を通す。Node.jsのAPIやsetTimeoutは使えない
- 開発中は claude –plugin-dir で読み込み、保存のたびにホットリロードされる。claude plugin validate で読まれ方を、claude plugin test でフックの動きを確かめる
- フックの実行時間は1つの出来事あたり10秒。modは利用者の権限で動き、サンドボックスの外にある
最終更新:2026年10月5日(Claude Code公式docs・変更履歴を確認。コード例は公式docsのもの)
modの最小構成。3ファイルと読み込み方
modは、次の3ファイルを持つプラグインのディレクトリです。公式チュートリアルの first-mod は、Claudeのツール呼び出しを数えてスピナーの横に出し、/tally コマンドで数を表示します。

first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.js
.claude-plugin/plugin.json はプラグインの定義(マニフェスト)です。modのための特別な項目はありません。
{
"name": "first-mod",
"version": "0.1.0",
"description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
"author": { "name": "Your Name" }
}
hooks/hooks.json は、コードの場所を指します。Claude Codeはプラグインを読み込む時にこのファイルを読み、modules キーにコードへのパスが書いてあれば、そのプラグインをmodとして扱います。つまり、modulesキーがあるとプラグインはmodになる、というのが唯一の条件です。パスは hooks.json からの相対で1つ書きます。
{
"description": "The first-mod hooks module",
"modules": ["./register.js"]
}
hooks/register.js がコード本体(hooks module)です。読み込み時にClaude Codeが register を呼び、on という関数を渡します。on を1回呼ぶたびに、出来事1つにハンドラ(フック)が1つ登録されます。拡張子は .js、.mjs、.cjs、.jsx、.ts、.mts、.cts、.tsx が使えます。
// The count, shared by the hooks below
let calls = 0
// Claude Code calls this once when the mod loads
export function register(on) {
// Runs when the session starts, before your first prompt
on('session.start', async ($, e, next) => {
// Add the /tally command
await $.command.register({
name: 'tally',
description: 'Show how many tool calls Claude has made',
})
// Let the session start as usual
return next(e)
})
// Runs each time Claude is about to use a tool
on('tool.call', async ($, e, next) => {
calls += 1
// Ask Claude Code to draw the interface again, so the new count shows
$.ui.invalidate('ui.render')
// Let the tool run as usual
return next(e)
})
// Runs when you type /tally, and only then, because of the matcher
on('command.run', { command: 'tally' }, async () => {
// The text to print in the transcript
return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
})
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
})
}
読み込みは、インストールせずに1セッションだけ使う –plugin-dir が開発向きです。シェルで claude –plugin-dir ./first-mod を実行すると、そのディレクトリが読み込まれ、ファイルを保存のたびにホットリロードされます。リロードのたびに register が実行し直されるので、上の calls は0に戻ります。対話セッションを開かずに確かめたい時は、claude -p “/tally” –plugin-dir ./first-mod で、first-mod: Claude has made 0 tool calls since this mod loaded と返ります。
フックの書き方。on(event, matcher, hook)と3つの引数
フックはどれも同じ形の関数です。引数は、$(mods API)、e(イベントの入力)、next(次のハンドラ)の3つです。$ は自分のコードの外に出るためのメソッドの集まり、e は出来事の入力(ツール名や引数など)の素のデータ、next は出来事をほかのmodとClaude Code本体へ渡して結果を返す関数です。

フックができることは3つです。first-modの4つのフックは、この3つをすべて使っています。
| 動き | 書き方 | first-modでの例 |
|---|---|---|
| 観て通す | 処理をして return next(e) | session.start でコマンドを登録し、tool.call で数えて描き直しを頼む。どちらも next(e) を返すので、いつもどおり進む |
| 書き換えて通す | return next({ …e, 変えたい項目 }) | ui.render で e のコピーの suffix に数を入れて next を呼ぶ。Claude Codeは元のスピナーに文字を足して描く |
| 自分で答えて止める | next を呼ばずに結果を返す | command.run が自分の結果を返す。/tally にはmod以外の動作が無いので next は呼ばない |
on の2番目の引数は絞り込み条件で、公式docsは matcher と呼びます。出来事の項目と比べるオブジェクトで、すべての項目が合った時だけフックが動きます。値には、文字列、許す値の配列、正規表現が使えます。
// A string matches one value: Bash calls only
on('tool.call', { tool: 'Bash' }, hook)
// An array matches any value in it: Edit calls and Write calls
on('tool.call', { tool: ['Edit', 'Write'] }, hook)
// A regular expression matches by pattern: every tool of one MCP server
on('tool.call', { tool: /^mcp__github__/ }, hook)
注意が2つあります。1つ目は、同じ出来事を matcher なしで2回登録するとモジュールが読み込まれないことです(on(“session.start”) is registered twice without a matcher)。セッション開始時にやることは1つのフックにまとめます。2つ目は、失敗した時の動きは.catchで決める、ということです。フックが例外を投げる、時間切れになる、形の違う結果を返す、のどれかが起きると、next を呼ぶ前ならそのフックは飛ばされ、次のハンドラが代わりに動きます。命令を止める役のフックが飛ばされると命令はそのまま走るので、止める側に倒したい時は .catch で代わりの答えを書きます。
// on returns a registration, and .catch attaches a handler to that one hook
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
// next.error.kind is 'throw' or 'timeout', which says how guard failed
return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
})
設定ファイルで書く従来のHooksとの関係は、Claude Code Hooks使い方とClaude Code Hooks通知設定を読んでおくと分かりやすくなります。設定ファイルのHooksは出来事のたびにシェルコマンドを起動してJSONを受け渡しますが、modのフックはClaude Codeの中に読み込まれたまま動き、状態を持てます。hooks.json には modules と並べて、従来のHooksを hooks キーの下に書くこともできます。
イベントの一覧。何に登録できるか
出来事は対象ごとにまとまっています。主なものと、フックが返せるものを公式リファレンスから抜き出しました。turn.step と process.spawn のフックは非同期ジェネレータで書き、ほかは非同期関数です。
| 分類 | イベント | 起きる時 | フックが返せるもの |
|---|---|---|---|
| ツール | tool.call | ツールが動く直前(サブエージェントの呼び出しとMCPのツールも含む) | next(e)、{ deny: 理由 }、{ result } |
| ツール | tool.check | tool.call とPreToolUseフックの後、実行してよいかを決める時 | { decision }(allow、ask、deny) |
| プロンプト | prompt.submit | プロンプトが送られた時 | next({ …e, text })、next({ …e, context })、{ drop: 理由 } |
| プロンプト | prompt.section、prompt.context、skill.prompt | システムプロンプトの節ごと、最初のメッセージに付く文脈、Skillの本文の展開 | { text }、{ blocks } など |
| コマンド | command.run | コマンドが動く直前 | { text }、{}、next(e) |
| ターン | turn.start、turn.step、turn.complete | ターンの開始、モデルへの要求1回ごと、ターンの終了 | next(e)。turn.step は next({ …e, model }) で別のモデルへ送れる。turn.complete は { text } で答えの下に1行出せる |
| セッション | session.start、session.end、session.compact | 最初のプロンプトの前(リロード後にもう一度)、終了、圧縮の直前 | next(e)。session.compact は { skip: 理由 } |
| サブエージェント | agent.spawn | サブエージェントやエージェントチームのメンバーが始まる直前 | next({ …e, model })、{ deny: 理由 } |
| 画面 | ui.render、ui.press、ui.input、ui.select | 描画場所が描かれる直前、modが描いた部品が使われた時 | 要素の木、または next(e) |
| 従来のHooks | classic.Stop、classic.PostToolUse など | 設定ファイルのHooksの各イベント | e はそのHookの標準入力のJSON |
よく使うのは tool.call と prompt.submit です。公式docsの例では、tool.call でgit push –forceを含むBashの命令を { deny: 理由 } で止めています。Claudeは理由をツールの結果として読むので、次に取れる行動を書きます。prompt.submit の例は、プロンプトがプルリクエストに触れている時だけ、現在のブランチ名をClaudeだけが読む文脈として足すものです。
on('prompt.submit', async ($, e, next) => {
// Pass on a prompt that doesn't mention a pull request as it is
if (!/\bPR\b|pull request/i.test(e.text)) return next(e)
const git = await $.process.run(['git', 'branch', '--show-current'])
// Outside a git repository the command fails, so there's no branch to add
if (git.exitCode !== 0) return next(e)
// Keep any context an earlier hook added, and add one more line for Claude
return next({ ...e, context: [...(e.context ?? []), 'Current branch: ' + git.stdout.trim()] })
})
プロンプトに関わるフックには費用面の注意があります。prompt.section や prompt.context などで、要求のたびに変わる文字を足すと、プロンプトキャッシュが無効になります。足す文字はできるだけ固定にします。
mods API($)の名前空間
フックの中には、Node.jsのAPIも、setTimeoutのようなタイマーも、ネットワークやファイルへの直接の口もありません。使えるのは標準のJavaScriptと、URL、TextEncoder、AbortController、crypto.subtle などのWeb APIだけです。外に出る操作は必ず$を通る、という決まりです。この作りのおかげで、Claude Codeは動かす前にmodが何を呼ぶかを静的に一覧できます。

| 名前空間 | できること |
|---|---|
| $.ui | 描画に関わる操作。resolve、invalidate、open、close、toast、status、log など |
| $.command | register でコマンドを足す。run、list |
| $.tool | register でClaudeが呼べるツールを足す。call、check、list |
| $.model | complete で、会話の履歴なしにモデルへ1つの質問を送る。maxTokensは既定1024 |
| $.session | messages()、cwd、model、usage() など。usage() はコンテキストの使用量と利用上限を返す。send でほかのセッションやサブエージェントへメッセージを送る |
| $.fs | read、write、exists、stat、list。1ファイル4MiBまで。write はその場で置き換えるので、複数のセッションが変えるデータは $.store に置く |
| $.process | run([‘git’, ‘status’]) でコマンドを起動し、終了を待つ。spawn は長く動くコマンドの出力を流す。run のタイムアウトは既定30秒、最大10分 |
| $.http | fetch(url, init)。{ status, ok, headers, text } を返す |
| $.store | プラグイン専用のJSONのキーと値の保存場所。セッションをまたいで残る。合計4MiBまで |
| $.clock | every と after がsetIntervalとsetTimeoutの代わり。遅延はミリ秒で先に書く |
| $.env、$.settings、$.mcp | 環境変数の読み書き、設定の読み取り、接続中のMCPサーバーのツール呼び出し |
1つの出来事より長く続く仕事は、session.start でタイマーを始めます。公式docsの例は、1分ごとにプルリクエストのチェックを調べてプロンプトの下に出すものです。フック自身の実行時間には上限がありますが、next の中と、$.clock.sleep を除くmods API呼び出しの待ち時間は数えません。
on('session.start', async ($, e, next) => {
// Call the function every 60,000 milliseconds, starting one minute from now
$.clock.every(60_000, async () => {
const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
// Replace the line under the prompt with the latest summary
$.ui.status('checks: ' + summarize(status.stdout))
})
// Return without waiting for the timer, so the session starts right away
return next(e)
})
コマンドとツールを足す
コマンドは利用者のためのもの、ツールはClaudeのためのものです。どちらも session.start で登録し、対応する出来事で答えます。
コマンドは $.command.register で登録し、command.run を名前で絞って答えます。返した text は会話に出て、Claudeも読みます。何も出さない時は {} を返します。Claudeが作業中でも動かしたいコマンドは、登録に immediate: true を付けます。
on('session.start', async ($, e, next) => {
// Add /standup to the command list, with the description the user sees there
await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })
return next(e)
})
// The matcher limits the hook to /standup, so other commands don't reach it
on('command.run', { command: 'standup' }, async ($, e) => {
// e.args is the text typed after the command name, or an empty string
return { text: 'Summary for the last ' + (e.args || '1') + ' day(s): ...' }
})
名前は、組み込みのコマンドと重ならないものにします。重なると $.command.register が例外を投げ(”/focus” refused: it is the built-in /focus のような文面)、例外を投げたフックは飛ばされるので、session.start の残りも動きません。登録はフックの最後に置くか、try と catch で囲みます。
ツールは $.tool.register に、名前、Claudeが読む説明、入力のJSON Schemaを渡します。Claudeからは mcp__、プラグイン名、アンダースコア2つ、登録名をつないだ長い名前で見えます。my-mod というプラグインが ticket を登録すると mcp__my-mod__ticket です。呼び出しは、その長い名前で絞った tool.call で受けます。
on('session.start', async ($, e, next) => {
await $.tool.register({
name: 'ticket',
// Claude decides when to call the tool from this description
description: 'Look up a ticket by its id and return its title and status',
// The arguments Claude has to send: one required string named id
inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
})
return next(e)
})
// The full tool name is mcp__, the plugin's name, and the registered name
on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {
// The tool's arguments are fields of e, so the id is e.id
const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))
// Return a result either way, so Claude learns when the lookup failed
return { result: response.ok ? response.text : 'Lookup failed with status ' + response.status }
})
外部システムとつなぐ道具をしっかり作るなら、MCPサーバーの方が向く場面もあります。違いはMCPサーバーの作り方で整理しています。指示を読ませるだけならClaude Code Skills ガイドのSkillで足ります。
画面を描く。ペイン・帯・要素
modが描ける場所は大きく2つです。Pane は、広いフルスクリーンの端末では会話の横のサイドバー、それ以外ではプロンプトの上の枠として出ます。会話の横か、プロンプトの上の枠、と覚えてください。mod が $.ui.openで開くまで出ません。AbovePrompt はプロンプトの真上の帯で、常にあり、すべてのmodが共有する場所です。

描くのは ui.render のフックです。matcher を付けないとすべての描画場所で動くので、{ component: ‘Pane’ } や { component: ‘AbovePrompt’ } で絞ります。ペインは e.requestId が自分の id かを確かめます。フックが返すのは要素の木です。要素は$.ui.resolve(e)から取る、と覚えてください。公式docsの hello-tabs から、ペインを開く部分と描く部分を抜き出すと次の形です。
// Runs when you type /hello-tabs
on('command.run', { command: 'hello-tabs' }, async ($) => {
// Open the pane, give it the keyboard, and let Esc close it
await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
// Print nothing in the transcript
return {}
})
// Runs each time Claude Code draws a pane
on('ui.render', { component: 'Pane' }, async ($, e, next) => {
// Leave other mods' panes alone
if (e.requestId !== PANE) return next(e)
// Get the elements this app can draw
const { Box, Text, Button } = $.ui.resolve(e)
// (中略)タブと本文の要素を組み立てて、Box の木を返す
})
$.ui.open の focus、closeOnEscape、holdToasts は true だけを受け付けます。false を渡すと ui.open: focus is true or left out のような例外になるので、付けない時は項目ごと省きます。帯は共有の場所なので、木を返すと自分より後ろのmodの描画を置き換えます。残したい時は await next(e) の結果を自分の Box の子に入れます。
| 要素 | 描くもの | 端末 | Desktop |
|---|---|---|---|
| Box | 行か列に並べる入れ物。flexDirection、columnGap、padding、borderStyle、width など | ○ | ○ |
| Text | 文字。color、bold、dimColor、italic、wrap | ○ | ○ |
| Button | 押せる部品。onPress、hotkey、plain | ○ | ○ |
| Input、Select | 入力欄(onSubmit、onInput)と選択肢(onSelect) | ○ | ○ |
| Markdown、Code、Link | Markdownとコードは10,000文字まで。Code は差分の表示にも使える | ○ | ○ |
| Svg | SVG文書(131,072文字まで) | × | ○ |
| Raster、Image | 色つきのセルの格子、PNGなどの画像 | ○ | × |
部品にはそれぞれ key を付けます。テストは key で部品を押したり入力したりするからです。modはキーボードを直接読みません。利用者がキーを押すと、Claude Codeがどの部品への入力かを決めてコールバックを呼びます。ペインがキーボードを受け取るのは、コマンドか押下から focus: true で開いた時、利用者がCtrl+Xの後にTabを押した時、クリックした時です。描き直しは $.ui.invalidate(‘ui.render’) で頼み、毎秒10回(端末の見えているペインと帯は30回)に抑えられます。
Claude Code自身が描く部分も描画場所です。ToolUse(ツール呼び出しの行)、Spinner、AskUserQuestion(Claudeが質問するダイアログ)などは、ui.render をその名前で絞れば書き換えられます。ただし権限の確認プロンプトは対象外で、modが変えることはできません。Claude Code側の表示や動作の設定はClaude Code /config完全ガイドにまとめています。
状態の置き場所。変数・$.state・$.store
値をどこに置くかで、どこまで残るかが決まります。開発中は保存のたびにモジュールが読み直されるので、変数に置いた値は消えます。
| 置き場所 | 残る期間 | 向くもの |
|---|---|---|
| モジュールの変数 | モジュールが読み直されるまで | 消えてよい値 |
| $.state | セッションの終わり、または /clear、/resume、/branch まで | 描画が頼る値。リロードをまたいで残したいもの |
| $.store | modが消すまで(どのセッションも読み書きしない状態が cleanupPeriodDays 続くと消える) | 設定、履歴など、次回も残っていてほしいもの |
$.state は反応する状態です。ui.render のフックが値を読むと購読になり、値を書くたびにその描画場所が描き直されるので、$.ui.invalidate を呼ぶ必要がなくなります。使うには、型宣言ファイルに値を宣言し、マニフェストの types でそのファイルを指し、モジュールで atom、read、update を使います。
import { atom, read, update } from 'claude-code'
// At the top of the module: name the value and give its default
const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)
// In the ui.render hook: read the value to draw it
const n = await read($, count)
// In a Button: write a new value from the old one
onPress: () => update($, count, (value) => value + 1)
plugin と key は文字列リテラルで書きます(claude plugin validate がソースから読むため)。値は型宣言に必ず書きます。ui.render のフックは状態を読めますが書けないので、書くのは onPress や onSubmit、ほかの出来事のフックからにします。
テストとデバッグ
確かめる道具は4つあります。型定義、claude plugin validate、claude plugin test、デバッグログです。どれもセッションを開かずに使えるか、開発中のセッションの横で使えます。

1つ目は型定義で補完することです。Claude Codeは、–plugin-dir で渡したディレクトリのmodを読み込むたびに、そのmodの .claude-plugin/types/ にTypeScriptの宣言ファイルを書き出します。動いている版のイベント、メソッド、要素がそのまま載るので、エディタの補完と tsc が追加の設定なしで効きます。公式docsは「イベントとメソッドは版で変わり得るので、食い違った時はこのページよりそのファイルを信じる」としています。
2つ目は claude plugin validate です。コードを動かさず、マニフェストを確かめ、読み込み時と同じ静的解析をかけます。2026年10月5日にmacOSのClaude Code 2.1.287で、公式docsの first-mod をそのまま置いて実行した出力(パスの表示行は省略)は次のとおりでした。
$ claude plugin validate ./first-mod
❯ ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner}
❯ ./register.js calls: $.command.register, $.ui.invalidate
✔ Validation passed
hooks: の行は登録した出来事と絞り込み条件、calls: の行は呼んでいるmods APIです。–strict を付けると警告もエラーとして扱い、–json で機械が読める形になります。
3つ目は claude plugin test です。ファイル名が .test.ts で終わるテストを、セッションもサインインもネットワークもなしに走らせます。テストはセッションもサインインも不要、という点がCIに向きます。テストの中の $ はClaude Codeの役で、$.tool.call などを呼ぶと同じ名前の出来事がmodのフックを通ります。Claude Codeが答えるはずの所は、on でスタブを登録して代わりに答えます。
import { expect, test } from 'claude-code/testing'
test('/tally reports the tool calls the mod has seen', async ($, on) => {
// Answer each tool call in Claude Code's place, so no tool runs
on('tool.call', () => ({ result: 'ok' }))
// Fire two tool calls, which the mod's tool.call hook counts
await $.tool.call({ tool: 'Bash', command: 'ls' })
await $.tool.call({ tool: 'Read', file_path: 'README.md' })
// Run /tally and check the text its hook returns
const answer = await $.command.run({ command: 'tally', args: '' })
expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')
})
同じ環境でこのテストを first-mod/tests/first-mod.test.ts に置いて実行すると、次の結果になりました。失敗があると終了コード1で終わるので、そのままCIに入れられます。
$ claude plugin test
tests/first-mod.test.ts:
(pass) /tally reports the tool calls the mod has seen [99.93ms]
1 pass
0 fail
Ran 1 test across 1 file. [0.45s]
テストキットには決まりがあります。スタブは、テストが最初に $ を呼ぶ前に全部登録します。session.start は自動では走らないので、そこで準備するフックがあるなら先に $.session.start を発火させます。next(e) を返すフックには、答えるスタブが要ります。1つのテストの時間は、timeoutMs を指定しなければ5秒です。
4つ目はデバッグログです。modが何もしない時は、まず validate を通し、次にClaude Codeが書く1行を読みます。–plugin-dir で読み込んだセッションでは会話に薄い行で出て、マーケットプレイスから入れたmodではデバッグログにだけ出ます。
claude --debug-file ./mod-debug.log --plugin-dir ./first-mod
tail -f ./mod-debug.log | grep first-mod
claude –debug でも同じログが書かれます。見るべきは hook skippedの行を読むことです。first-mod: tool.call hook skipped: threw Error: boom のように、modの名前、出来事、理由が1行で出ます。例外、時間切れ、形の違う結果のどれかです。保存したコードが壊れている時は reload failed, the previous version stays loaded: と理由が出て、最後に動いていた版が動き続けます。自分でログに書きたい時は $.ui.log(‘message’, { to: ‘debug’ }) です。
共有とバージョンの扱い
modはプラグインなので、共有の仕方もプラグインと同じです。数人ならディレクトリかzipを渡し、チームなら自分たちのマーケットプレイス(プラグインごとにディレクトリを持つリポジトリ)に載せます。公式のチュートリアルは、入れる側の手順を3つのコマンドで示しています。
/plugin marketplace add your-org/my-mods
/plugin install token-weather@my-mods
/reload-plugins
公開する前に確かめることが3つあります。プラグインの name が claude- で始まるなど、Anthropicのものに見える名前は claude plugin validate が通しません。イベントとメソッドは版で変わり得るので、READMEに試したClaude Codeの版を書きます。開発は入れたコピーでなく –plugin-dir のディレクトリに対して続けます。入れたプラグインは版ごとにキャッシュされるので、マニフェストの version を上げて入れ直すまで編集が届きません。プラグイン全体の標準化の流れはAgent Pluginsとはで扱っています。
上限と落とし穴
| 項目 | 上限 |
|---|---|
| フック自身の実行時間(1つの出来事あたり) | 10秒。prompt.edit のフックは50ミリ秒。.catch のハンドラは1秒 |
| session.end のフック全体 | SessionEnd の予算と同じ(変えなければ1.5秒) |
| $.process.run | 既定30秒、最大10分 |
| $.model.complete の maxTokens | 既定1024、最大64,000かモデルの出力上限 |
| $.fs.read と $.fs.write | 1ファイル4MiB |
| $.session.messages() | 新しい方から4,096件 |
| コマンド、ツール、ペインの名前 | 英数字と _ と -、64文字まで |
- 入れたmodは1つのワーカースレッドを共有します。await しないループのようにスレッドを止めるフックがあると、first-mod was unloaded: it crashed the hooks worker と出てそのmodが外されます。原因を特定できないままワーカーが3回止まると、組み込み以外のmodがすべて外されます
- フックが止める役(deny)を持つなら、.catch を付けて失敗時も止める側に倒します。付けないと、失敗したフックは飛ばされて命令が走ります
- modは利用者の権限でそのまま動き、サンドボックスの外にあります。サンドボックスが隔離するのはClaudeが動かすBashの命令で、modが起動したプロセスは対象外です。認証情報の扱いはClaude Code サンドボックスmaskモード解説とあわせて設計してください
- 組織によっては、managed settingsの allowManagedModsOnly で利用者のmodが読み込まれません。その場合、–plugin-dir のmodも、Claudeに書かせたmodも動きません
- VS Code拡張のチャットパネル、claude -p、Agent SDKではフックは動きますが、描画は出ません。描くmodは、出せない所では会話の1行やコマンドの返事に落とす作りにします
よくある質問
Q. TypeScriptで書くのに、ビルドの設定は要りますか?
A. 要りません。コード本体は .ts や .tsx でも読み込まれます。modに tsconfig.json が無ければ、Claude Codeが生成した設定を継ぐものをmodの直下に足すので、エディタと tsc -p ./first-mod がそのまま型を確かめられます。
Q. npmのパッケージやNode.jsのAPIは使えますか?
A. フックの中にNode.jsのAPIはありません。ファイル、プロセス、ネットワークは $.fs、$.process、$.http を通します。URL、TextEncoder、AbortController、crypto.subtle などの標準のWeb APIは使えます。テストファイルからは、mod自身のファイルと隣の .ts を読み込めます。
Q. Claudeに書かせたmodを、次のセッションでも使うには?
A. Claudeが書いたmodは ~/.claude/dev-mods/ の下のセッション専用フォルダに置かれ、そのセッションでしか読み込まれません。残す時はディレクトリを自分の場所へ写し、claude –plugin-dir で読み込むか、マーケットプレイスに載せます。
Q. サブエージェントのツール呼び出しにもフックは効きますか?
A. 効きます。tool.call はサブエージェントの呼び出しとMCPのツールの呼び出しでも起きます。ターンの出来事では e.agentId でサブエージェントのものかを見分けられ、agent.spawn では開始前にモデルを選んだり { deny: 理由 } で止めたりできます。
Q. 複数のmodが同じ出来事を扱うと、どの順で動きますか?
A. 1本の鎖になります。先に読み込まれたmodが外側で、出来事を先に見て結果を最後に見ます。順番は、組織のガードと組織のmod、利用者が入れたmod、組織が後ろに指定したmod、そのほかの組み込みのmod、の順です。自分が入れたmodの中では、マニフェストの dependencies に書いたmodより前に動きます。
Q. 2.1.287より後の版で変わった点はありますか?
A. 変更履歴では、2.1.288で $.ui.selection()(フルスクリーンで最後に選択した文字を返す)が加わり、2.1.289で ui.fault(modが描いたClientの失敗を知らせる出来事)と、チームメンバー向けの agent.spawn が加わっています。どちらの版もmod関連の修正を多く含むので、試す時は claude –version で版を確かめてください。
まとめ
Claude Code Modsは、プラグインの hooks/hooks.json に modules を書き、register(on, options) を書き出すコードを置くだけで始められます。フックは ($, e, next) を受け取り、観て通す、書き換えて通す、自分で答えて止める、のどれかを選びます。外に出る操作はすべて $ を通るので、claude plugin validate が動かす前に呼び出しを一覧でき、claude plugin test がセッションなしでフックを検証できます。開発は –plugin-dir とホットリロードで回し、残したい値は $.state か $.store に置き、止める役のフックには .catch を付けます。公開する時は、試した版をREADMEに書き、version を上げて入れ直す運用にします。
運営元 Uravation よりAIエージェントを構想から本番運用まで進める順番と、体制・KPIの決め方をまとめた資料を無料で公開しています。 AIエージェント導入ロードマップを受け取る(無料)
参考・出典
- Mods overview — Claude Code docs(参照日: 2026-10-05)
- Create a mod — Claude Code docs(参照日: 2026-10-05)
- React to events with a mod — Claude Code docs(参照日: 2026-10-05)
- Use the mods API — Claude Code docs(参照日: 2026-10-05)
- Draw in the interface with a mod — Claude Code docs(参照日: 2026-10-05)
- Test a mod — Claude Code docs(参照日: 2026-10-05)
- Troubleshoot a mod — Claude Code docs(参照日: 2026-10-05)
- Mods reference — Claude Code docs(参照日: 2026-10-05)
- Claude Code changelog(2.1.287〜2.1.289)(参照日: 2026-10-05)
- Customize Claude Code with mods — Anthropic(Claude blog)(2026-10-01/参照日: 2026-10-05)
- Getting started with Claude Code mods — claude.dev Blog(2026-10-01/参照日: 2026-10-05)
- anthropics/claude-code / mods(組み込みmodのソースと型定義)(参照日: 2026-10-05)
