Skip to content

Claude Code Mods 入門:最初の Mod の作り方・テスト・導入手順と、コミュニティ実例 15 選

更新日

Claude Code Mods(v2.1.287 以降)は、TypeScript の関数でペインを描画したり、即時実行のスラッシュコマンドを追加したり、ツール呼び出しをガードしたりできるプラグインです。動作確認済みの Mod 3 つの作り方とテスト、コミュニティ実例 15 選、導入前の安全チェックまで、入門者向けに使い方を解説します。

Claude Code の Mod は、Claude Code の内部で動く JavaScript または TypeScript の関数でできたプラグインです。ペインやプロンプト上部の行を描画したり、即座に実行されるスラッシュコマンドを追加したり、ツール呼び出しを実行前に止めたり書き換えたりできます。Mods は 2026年10月1日リリースの Claude Code 2.1.287 で導入され、デフォルトで有効になっています。

目的別の最短ルートは次のとおりです。

やりたいこと方法
コードを書かずに Mod を手に入れたいセッションで作りたい Mod を説明します。たとえば「各ターンにかかった時間を表示する Mod を作って」と頼むと、Claude がコードを書き、ホットリロードを有効にするか一度だけ確認してきます
他の人が作った Mod を導入したいターミナルのセッションで /plugin install <name>@<marketplace>、またはシェルで claude plugin install <name>@<marketplace> を実行します
開発中のフォルダを読み込みたいclaude --plugin-dir ./my-mod
組み込みのサイドエージェントを試したい/plugin enable cc-plugin-you-should-know@builtin

まずはバージョンを確認しましょう。claude --version が 2.1.287 以降を表示すれば使えます。Desktop アプリは独自の Claude Code を同梱していて、こちらは 2.1.286 から Mods が動きます。Code タブで /status を入力すると、そこで動いているバージョンを確認できます。

筆者が試したこと:2026年10月7日、macOS の Desktop アプリの Code タブで、Claude Code 2.1.293 を使って 3 つの Mod を作りました。コマンドガード、プロンプト上部のターンメーター、Python 作業用の /pyenv コマンドです。3 つとも claude plugin validate を通過し、claude plugin test による計 8 件の自動テストにもパスしています。ガードは、ホットリロードを有効にしたセッションでも実際に動かしました。コミュニティに関する節は、Zenn・Qiita・note に 10月1〜7日に公開された日本語記事約 50 本、公式の Mods ドキュメント (opens in a new tab)、10月6日時点の awesome-claude-code-mods (opens in a new tab) カタログのスキャンデータをもとにしています。

Mod とは何か、ほかの拡張方法との使い分け

Mods が登場するまで、Claude Code は外側から拡張するものでした。手段は settings hook(settings.json に書く従来のフック)、スキル、ステータスラインのスクリプト、MCP サーバーなどです。違いはコードが動く場所にあります。settings hook は、Claude Code が外から起動するスクリプトです。一方、Mod の関数は Claude Code 自身のプロセスの中で動きます。そのため、インターフェースに描画できるのは Mod だけです。

公式の概要ページでは、おおよそ次のように比較されています。

ModSettings hookスキルMCP サーバー
実体プラグイン内の JS / TS 関数settings.json に書くシェルコマンド、HTTP 呼び出し、プロンプト指示を書いた SKILL.md ファイルClaude にツールを提供する外部プロセス
UI への描画できるできないできないできない
変更できる対象ツール呼び出し、プロンプト、コマンド、ターン、UI呼び出しを通すかどうか、その引数と結果、追加のコンテキストClaude の知識と行動Claude が使えるツール
向いている場面ペイン、バンド、即時実行のコマンドが欲しいとき、イベントを書き換えたいときブロックやログ記録をするスクリプトがすでにあるとき同じ指示を何度も貼り付けているときClaude を外部システムにつなぐ必要があるとき

名前の紛らわしさに注意してください。Mods のドキュメントで「hook」と書かれていたら、それは Mod の関数のことです。従来の仕組みは「settings hook」と呼ばれています。この記事でも「フック」は Mod の関数を指します。settings hook は今も動作し、非推奨にもなっていません。1 つのプラグインに Mod、スキル、MCP サーバーをまとめて入れることもできます。

Mods が動く場所

フックはほぼどこでも動きますが、描画されるのはターミナルと Desktop アプリだけです。

Claude Code の実行環境フックMod の UI
ターミナルの claude(エディタ内のターミナル、JetBrains を含む)動く表示される
Desktop アプリの Code タブ動く表示される(ターミナル専用の要素を除く)
Desktop アプリの WSL セッション動かない表示されない
VS Code 拡張のチャットパネル動く表示されない
claude -p と Agent SDK動く表示されない
クラウドセッションプラグインがセッションに届いていれば動く表示されない

この表は公式の概要ページにもとづいています。つまり、ガード系の Mod は VS Code や claude -p でも守ってくれますが、メーター系の Mod は描画する場所がありません。

Claude Code が 2 つ入っている環境にも注意が必要です。日本語の記事では、複数の著者が紛らわしい validate エラーに遭遇しています。原因は、Homebrew で入れた古いターミナル版 CLI(報告ではバージョン 2.1.226 と 2.1.234)が、Desktop アプリ同梱の 2.1.286 と共存していたことでした。使う場所ごとにバージョンを確認してください。

Mod のファイル構成

小さな Mod なら、ファイルは 3 つで済みます。

safe-shell/
├── .claude-plugin/
│   └── plugin.json      name, version, description
└── hooks/
    ├── hooks.json       { "modules": ["./register.ts"] }
    └── register.ts      your code

register.ts は register(on) という関数を 1 つ export します。on(event, matcher, hook) を呼ぶたびにフックが 1 つ追加され、どのフックも同じ 3 つの引数を受け取ります。

  • $ はエンジンです。自分のコードの外にあるものは、すべて $ を経由して使います。$.ui.toast、$.process.run、$.state、$.model.complete などです。
  • e はイベントです。たとえば、Claude がこれから実行しようとしている Bash コマンドがこれにあたります。
  • next(e) は、イベントをほかの Mod に渡し、最後に Claude Code に渡します。呼ばずに return すれば、そのフック自身が応答したことになります。next({ ...e, command }) のように呼べば、実際に起きる処理を変えられます。

モジュールからは Node も DOM も使えません。この制約があるからこそ、Claude Code は Mod が呼び出すものを実行前にすべて列挙できます。手元の Mod フォルダで試してみてください。

claude plugin validate ./safe-shell

Mod 1:破壊的なコマンドをブロックするガード

auto モードや Bypass permissions で Claude を動かしていると、git reset --hard の前に Claude を止めるものは何もありません。この Mod は 3 種類のシェルコマンドを拒否し、.env ファイルを会話に持ち込ませません。

import type { Register } from 'claude-code'
 
// Shell commands an agent should never run without a human typing them.
const BLOCKED: { pattern: RegExp; reason: string }[] = [
  {
    pattern: /\brm\s+-[a-zA-Z]*[rR][a-zA-Z]*\s+(\/|~|\$HOME)\/?\*?(\s|$)/,
    reason: 'recursive delete of / or the home folder',
  },
  {
    pattern: /\bgit\s+push\b.*(--force\b|--force-with-lease\b|\s-f\b).*\b(main|master)\b/,
    reason: 'force push to main or master',
  },
  {
    pattern: /\bgit\s+reset\s+--hard\b/,
    reason: 'git reset --hard throws away uncommitted work',
  },
]
 
// .env, .env.local, config/.env.production ... but not .env.example.
const isSecretEnvFile = (path: string) =>
  /(^|\/)\.env(\.[^/]+)?$/.test(path) && !/\.(example|sample|template)$/.test(path)
 
export const register: Register = on => {
  on('tool.call', { tool: 'Bash' }, ($, e, next) => {
    const hit = BLOCKED.find(rule => rule.pattern.test(e.command))
    if (hit === undefined) {
      return next(e)
    }
    $.ui.toast(`safe-shell blocked: ${hit.reason}`)
 
    return { deny: `safe-shell: blocked (${hit.reason}). Ask the user to run it themselves.` }
  }).catch(($, e, next) =>
    // Fail closed: if the guard itself breaks, refuse instead of letting the command through.
    next.called ? next(e) : { deny: 'safe-shell: the guard failed, so the command was not run.' },
  )
 
  for (const tool of ['Read', 'Edit', 'Write'] as const) {
    on('tool.call', { tool }, ($, e, next) =>
      isSecretEnvFile(e.file_path)
        ? { deny: `safe-shell: ${e.file_path} holds secrets and stays out of the conversation.` }
        : next(e),
    ).catch(($, e, next) =>
      next.called ? next(e) : { deny: 'safe-shell: the guard failed, so the file was not touched.' },
    )
  }
}

正規表現そのものより大事なポイントが 2 つあります。

ガードは next を呼ぶ前に判定する。next が動いた時点で、コマンドはもう実行されています。そのあとで deny を返しても、何も取り消せません。

.catch で fail closed にする。例外を投げたフックや、10 秒の持ち時間を超えたフックはスキップされます。そしてスキップされたガードは、コマンドをそのまま通してしまいます。next.called ? next(e) : deny のハンドラーは、その場合でも通さずに拒否します。validate は判定を行うフック(gating hook)をすべて列挙し、それぞれに .catch があるかどうかも表示します。他人が作ったガードでは、最初にここを確認してください。

❯ ./register.ts hooks: tool.call{tool=Bash}, tool.call{tool=Read|Edit|Write}
❯ ./register.ts gating hook with .catch: tool.call{tool=Bash}
❯ ./register.ts gating hook with .catch: tool.call{tool=Read|Edit|Write}
❯ ./register.ts calls: $.ui.toast
✔ Validation passed

セッションなしでテストする

claude plugin test は、Mod の *.test.ts ファイルをエンジン上で実行します。セッションもサインインもネットワークも不要です。ただし、テストキットでは Mod の下に何もありません。そのため各テストで、Mod が次に渡す処理を受け止める代役のフック(スタブ)を登録します。

import { expect, test } from 'claude-code/testing'
 
const ran = { stdout: 'ran', stderr: '', interrupted: false }
 
test('blocks rm -rf on the home folder', async ($, on) => {
  on('tool.call', { tool: 'Bash' }, () => ({ result: ran }))
  const answer = await $.tool.call({ tool: 'Bash', command: 'rm -rf ~' })
  expect(answer.deny).toContain('recursive delete')
})
 
test('lets ordinary commands through', async ($, on) => {
  on('tool.call', { tool: 'Bash' }, () => ({ result: ran }))
  const answer = await $.tool.call({ tool: 'Bash', command: 'rm -rf ./build && python -m pytest -q' })
  expect(answer.deny).toBeUndefined()
})

ファイル全体では 4 件のテストがあります。

(pass) blocks rm -rf on the home folder [16.23ms]
(pass) blocks a force push to main [7.73ms]
(pass) lets ordinary commands through [7.22ms]
(pass) keeps .env out of Read but allows .env.example [9.40ms]

 4 pass
 0 fail

筆者の最初の実行は、on("tool.call") after the test first called $ というエラーで失敗しました。代役のフックは、各テストの冒頭、最初に $ を呼ぶ前に登録してください。

実際のセッションでの挙動

ホットリロードを有効にした状態で、Claude に echo "the words git reset --hard inside an echo" を実行させてみました。ガードは「blocked (git reset --hard throws away uncommitted work)」として拒否し、トーストも表示されました。しかし、この echo は無害です。パターンマッチでは、コマンドと、コマンドに言及しているだけのテキストを区別できません。変数、スクリプト、エイリアスに隠れたコマンドも見逃します。rafi-guard、delete-guard、board-guard など、似たガードを公開した国内の開発者も、この 2 つの問題を報告しています。

ガード系の Mod は、2 枚目のセーフティネットとして扱いましょう。絶対に起きてはいけない操作は、パーミッションの deny ルールにも入れておきます。逆方向のリスクにも注意してください。公式ドキュメントによると、managed settings(組織が配布する管理設定)のない個人のマシンでは、deny ルールが拒否する呼び出しを Mod が承認できてしまいます。Mod はサンドボックス化されておらず、ユーザー自身の権限で動きます。

Mod 2:プロンプト上部のメーター

Mod の描画先として多いのは、プロンプト上部のバンドとサイドペインです。10月6日のカタログのスキャンでは、41% の Mod がバンドに描画し、40% がペインを開いていました。ここで作る Mod は、直前のターンの所要時間、ツール呼び出し数、トークン数を表示します。Hide ボタンで隠すこともできます。

描画で読む値は $.state に置き、それぞれを小さな型定義ファイルで宣言します。

// types/index.d.ts, named in plugin.json as "types": "./types/index.d.ts"
export type TurnStats = {
  seconds: number
  tools: number
  inputTokens: number
  outputTokens: number
  model: string | null
}
 
declare module 'claude-code' {
  interface PluginState {
    'turn-meter': { last: TurnStats | null; isHidden: boolean }
  }
}

フックでツール呼び出しを数え、turn.complete から durationMs と usage を読み取って、バンドを描画します。

import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'
 
import type { TurnStats } from '../types'
 
const last = atom({ plugin: 'turn-meter', key: 'last' } as const, null)
const isHidden = atom({ plugin: 'turn-meter', key: 'isHidden' } as const, false)
 
const formatTokens = (n: number) => (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n))
 
export const register: Register = on => {
  let tools = 0
 
  on('prompt.submit', ($, e, next) => {
    tools = 0
    return next(e)
  })
 
  on('tool.call', ($, e, next) => {
    // Count the main conversation's calls; subagents carry an agentId.
    if (e.agentId === undefined) tools += 1
    return next(e)
  })
 
  on('turn.complete', async ($, e, next) => {
    if (e.agentId !== undefined) return next(e)
    const usage = e.usage
    const stats: TurnStats = {
      seconds: Math.round(e.durationMs / 1000),
      tools,
      inputTokens: usage
        ? usage.input_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens
        : 0,
      outputTokens: usage?.output_tokens ?? 0,
      model: usage?.model ?? null,
    }
    await update($, last, () => stats)
    if (stats.seconds > 120) $.ui.toast(`That turn took ${stats.seconds}s`)
    return next(e)
  })
 
  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
    const stats = await read($, last)
    if (e.props.hasSurvey || stats === null || (await read($, isHidden))) return next(e)
 
    const { Box, Button, Text } = $.ui.resolve(e)
    const model = stats.model === null ? '' : ` · ${stats.model}`
    // The band is shared by every mod: keep what the mods beneath drew, then add a row.
    const below = await next(e)
 
    return (
      <Box flexDirection="column">
        {below}
        <Box>
          <Text dimColor>
            {`Last turn: ${stats.seconds}s · ${stats.tools} tool calls · ${formatTokens(stats.inputTokens)} in / ${formatTokens(stats.outputTokens)} out${model} `}
          </Text>
          <Button key="hide" label="Hide" onPress={() => update($, isHidden, () => true)} />
        </Box>
      </Box>
    )
  })
}

テストでは、ターミナルと Desktop のどちらのサーフェスでも、バンドは次のように表示されました。

Last turn: 42s · 0 tool calls · 18.2k in / 800 out · claude-opus-5-5 [ Hide ]

作ってみてわかったことが 3 つあります。

  • 描画する値は、モジュール変数ではなく $.state に置きます。ホットリロードでは register が再実行されて変数がリセットされますが、$.state は残ります。/clear すると最初からになるので、それより長く残したい値は session.end で $.store に書き込みます。
  • バンドは全 Mod で共有されます。next(e) を呼び、その結果を自分の Box の中に入れてください。そうしないと、ほかの Mod の行がすべて隠れてしまいます。ある note の著者は、この仕組みに気づくまで、自作のバンドの 1 つを別の Mod に隠されていました。
  • Text 要素は key を保持しません。テストでは ui.find({ type: 'Text', text: /Last turn/ }) のように、表示している文字列で探します。

Mod 3:Claude が別の Python を使ってしまうときの /pyenv

データサイエンス向けの Mod を探したところ、空白地帯が見つかりました。10月6日のスキャンに含まれる 2,690 個の Mod のうち、名前や説明で Jupyter、pandas、DataFrame、Polars、venv、conda に触れているものは 1 つもありません。

この Mod が扱う問題は、データ分析ではよくあるものです。Claude の Bash ツールは PATH 上で最初に見つかった python3 を実行しますが、それがプロジェクトの環境とは限りません。筆者のマシンで環境チェック用のスクリプト(以下、プローブ)を実行すると、次のように出力されました。

python: /Users/<you>/.local/bin/python3 (3.14.2)
env: none (system or tool-managed Python)
pandas: 3.0.1
numpy: 2.4.2
polars: not installed
matplotlib: 3.11.0
pygwalker: not installed

仮想環境が有効になっていなかったため、もし Claude が pip install を実行していたら、グローバルのインタープリターにインストールされていたはずです。

/pyenv コマンドはこのプローブを実行し、2 つの部分で応答します。text は画面に表示される行です。context はモデルだけが読むメモです。これにより Claude の次の Bash 呼び出しは、ユーザーが改めて指示しなくても、どのインタープリターが使われているかを把握できます。

import type { Register } from 'claude-code'
 
// Reads package versions from metadata instead of importing them, so it stays fast.
const PROBE = `
import os, sys
from importlib.metadata import PackageNotFoundError, version
env = os.environ.get("VIRTUAL_ENV") or os.environ.get("CONDA_PREFIX") or "none (system or tool-managed Python)"
print(f"python: {sys.executable} ({sys.version.split()[0]})")
print(f"env: {env}")
for name in ("pandas", "numpy", "polars", "matplotlib", "pygwalker"):
    try:
        print(f"{name}: {version(name)}")
    except PackageNotFoundError:
        print(f"{name}: not installed")
`
 
export const register: Register = on => {
  on('session.start', async ($, e, next) => {
    await $.command.register({
      name: 'pyenv',
      description: "Show which Python, environment and data packages this session's shell uses",
    })
    return next(e)
  })
 
  on('command.run', { command: 'pyenv' }, async $ => {
    try {
      const probe = await $.process.run(['python3', '-c', PROBE], { timeoutMs: 15_000 })
      if (probe.exitCode !== 0) {
        return { text: `pyenv: python3 exited with ${probe.exitCode}: ${probe.stderr.trim().slice(0, 300)}` }
      }
      const report = probe.stdout.trim()
      // `text` is the row you see; `context` is a note only the model reads.
      return {
        text: report,
        context: [`Python environment of this session's shell, from /pyenv:\n${report}`],
      }
    } catch (error) {
      return { text: `pyenv: could not run python3 (${String(error).slice(0, 200)})` }
    }
  })
}

Mod のスラッシュコマンドは、モデルのターンを挟まずに関数をすぐ実行します。Claude が作業中でも動きます。$.process.run はシェルを通さず引数のリストを受け取り、timeoutMs を指定しなければ 30 秒でタイムアウトします。

$.process.run は Claude の Bash ツールと同じ PATH を見ているのでしょうか。Mod から起動したプロセスで nvm の node が見つからなかったという報告があったので、一時的な調査用 Mod で確かめました。筆者の Desktop セッションでは、そのままの $.process.run は Bash ツールとまったく同じ PATH を受け取り、python3 も同じものを指していました。逆に、ログインシェル zsh -lc 経由で同じ調査を走らせると、python.org の 3.10.9 という別のインタープリタを返しました。非対話のログインシェルは ~/.zshrc を読まず、PATH の追加設定の多くはそこにあるためです。呼び出しはシェルを挟まないまま使いましょう。結果がおかしいと感じたら、Claude Code のプロンプトで !which python3 を実行して見比べてください。

テストでは、もう 1 つ落とし穴が見つかりました。$.process.run や $.command.register のような $ の呼び出しにも代役が必要で、$ 呼び出しの代役は { value: ... } の形で応答します。結果をそのまま返すと、テストキットに拒否されます。

分析の大半をスクリプトではなくノートブックで行っているなら、この方法には限界があります。Claude Code は .ipynb ファイルを JSON として編集するだけで、実行中のカーネルを見ることはないからです。RunCell (opens in a new tab) はセルを実行し、その出力を直接読むノートブック向けのエージェントなので、こうした作業にはこちらのほうが向いています。

Mod を Claude に書かせる

ここまでのコードは、手で書く必要はありません。セッションで「5 時間枠の使用量をプロンプトの上に表示する Mod を作って」のように頼むだけです。Claude は組み込みの plugin-authoring スキルを読み込み、~/.claude/dev-mods/<session-id>/ にファイルを書き出します。最初のファイルを書くときに、「Enable hot reloading for this session?」という確認が一度だけ出ます。同意すると、以降は Claude のターンが終わるたびに編集内容がリロードされます。上の 3 つの Mod も、この方法で作って読み込みました。

ドキュメントとコミュニティからの補足です。

  • フォルダは恒久的な場所にコピーしておきましょう。Qiita や Zenn の著者によると、dev-mods フォルダは約 30 日たつと古いセッションと一緒に削除されます。
  • 開発中は claude --plugin-dir ./my-mod で起動します。インストールした Mod はキャッシュされたコピーで動くため、元のフォルダを編集しても、プラグインを更新するまで何も変わりません。
  • Desktop アプリには起動フラグを渡せません。代わりに ~/.claude/settings.json の env ブロックで CLAUDE_CODE_PLUGIN_DIRS を設定します。プロジェクトの設定ファイルに書いても、この設定は無視されます。
  • /plugin を実行して、1 mod active · safe-shell のような薄い色の行が出れば、Mod は読み込まれています。
  • 開発中は claude --debug で起動しましょう。デバッグログには、スキップされたフックと、エンジンが拒否した描画がすべて記録されます。

参考になるコミュニティ製 Mod 15 選

スター数 316 の awesome-claude-code-mods (opens in a new tab) カタログは、リリースから 5 日後の 10月6日に、1,265 のリポジトリにある 2,690 個の公開 Mod をスキャンしました。そのデータから、どんな Mod が作られているかが見えてきます。

  • 68% がスラッシュコマンドを追加
  • 53% がツール呼び出しをフック
  • 41% がプロンプト上部に描画、40% がペインを開く
  • 40% がプロセスを起動、12% がモデルを呼び出し、11% がネットワークを使用

幅の広さがわかる 15 個を紹介します。スター数は 2026年10月7日時点のものです。

Mod内容スター注目ポイント
terminal-browser (opens in a new tab)会話の横に Web ブラウザを表示し、Web サイト、ローカル HTML のプレビュー、プルリクエストを閲覧できる3,695現時点で最も野心的なペイン
claude-image-view (opens in a new tab)貼り付けた画像を、[Image #1] の代わりにサムネイルでプロンプト上部に表示160ターミナルでの画像描画
hamzafer/claude-code-mods (opens in a new tab)コンテキストバー、blast-radius ガード、Markdown プレビュー、サブエージェントのダッシュボードをまとめたセット1381 つのリポジトリに複数タイプの Mod
claude-auto-handoff (opens in a new tab)長くなったセッションを、構造化した引き継ぎメモ付きで新しいセッションに渡す60コンテキスト管理
prismantis (opens in a new tab)返信をテーマで装飾。表、コード、チャートをボックスアートで描き、右から左に書くテキストにも対応49Claude 自身のメッセージの見た目を変える手法
cc-arcade (opens in a new tab)プロンプト上部で遊べる 9 つのゲーム。Claude の処理が終わると一時停止41フレームクロックを使ったアニメーション
jev-permission-gate (opens in a new tab)auto モードのツール呼び出しを Jev の判定モデルが審査28丁寧なプライバシーの説明。直近 3 件のメッセージを各 1,500 文字まで TypeSafe の API に送ると明記
prompt-rail (opens in a new tab)過去のプロンプトをレール状に並べ、ホバーで内容を表示、クリックでその位置にジャンプ22Zenn で最もいいねを集めた日本語の Mods レビュー記事の主役
claude-paste-view (opens in a new tab)貼り付けた画像や長いテキストをプレビュー18小さく読みやすいコード
claude-gfm-render (opens in a new tab)返信内の GitHub アラート、タスクリスト、Mermaid を、ボックスアートまたは SVG で表示14サーフェスごとに描画を切り替える方法
claude_qamods (opens in a new tab)qa-guide が、Claude からの質問と選択肢をサイドペインで解説10組み込みダイアログの改善
CC-Usage-Band (opens in a new tab)5 時間枠と 7 日間の利用上限、コンテキスト、キャッシュヒット率をプロンプト上部に表示10最も多いタイプであるメーター系の例
harness-scope (opens in a new tab)リポジトリごとのプロファイルで、グローバルなスキル、エージェント、ルールを非表示にする1作者の計測では、スキル一覧が 98 件から 49 件に減少
touch-map (opens in a new tab)Claude がどのファイルを一覧、読み取り、編集、作成したかを表示0作者は、あるリファクタリングの調査中に Claude が 304 ファイル中 125 ファイルに触れていたことを確認
claude-mods-router (opens in a new tab)$.model.classify でプロンプトごとに effort レベルを振り分ける0Qiita の解説記事(26 いいね)が、モデルを切り替えるとプロンプトキャッシュが効かなくなると指摘

Anthropic も claude-code-playground (opens in a new tab) で、token-weather、blast-radius、replay-theater の 3 つのサンプルを公開しています。/diff を含む組み込み Mod のソースは、claude-code リポジトリ (opens in a new tab) にあります。

コミュニティで定着したベストプラクティス

リリース後の 1 週間で、日本語の記事だけでも約 50 本が公開され、動く Mod、計測結果、失敗の報告が共有されました。そこで何度も挙がった習慣をまとめます。

  1. 導入する前に Mod を読む。リポジトリを clone して claude plugin validate を実行し、hooks: と calls: の行を確認します。$.process、$.http、$.fs.write、$.env.get、$.model が出てくる箇所はソースを読みましょう。更新のたびに validate し直してください。validate を通過しても、エンジンがコードを読み込めるというだけで、コードが安全だという意味ではありません。
  2. ガードは next の前に判定し、fail closed にする。監視するだけの Mod は、逆に .catch(($, e, next) => next(e)) で fail open にします。こうすれば、メーターのバグで作業が止まることはありません。
  3. 待つなら $ の呼び出しの中で待つ。フックが自分の処理に使える時間は 10 秒です。実行中の $.process.run や $.ui.ask の待ち時間はこれに数えられませんが、自分で作った Promise の待ち時間は数えられます。
  4. $ を保存したり分割代入したりしない。$ は、ファイルのトップレベルで宣言した関数にだけ渡します。複数の著者が、これを直すまで Mod がエラーも出さずに読み込まれなかったと報告しています。
  5. プロンプトキャッシュに気を配る。セッションの途中でモデルを切り替えたり、リクエストごとにシステムプロンプトを変えたりすると、キャッシュが捨てられます。
  6. e.surface で分岐する。Svg は Desktop アプリでしか描画されず、Raster と Image はターミナルでしか描画されません。
  7. チームで使うならバージョンを固定する。API はアーリーアクセス段階で、リリースごとに変わります。公開後に出た 6 つのリリース(2.1.288〜2.1.293)のうち 5 つで、Mod の挙動が変更または修正されました。

シークレットのマスキングは、教訓になる事例です。ある note の著者は、ツールの出力に含まれる API キーを、Claude が見る前に置き換える Mod を作りました。それでも Claude は、失敗した編集を od -c でデバッグする途中で生の値を表示しました。さらに、ファイル全体を書き直す Write で、本物のキーがプレースホルダーに上書きされました。最後まで守れたのは、そのファイルに対するパーミッションの deny ルールだけでした。画面の表示を変える Mod は、モデルからは何も隠せません。

よくあるトラブルと対処法

症状考えられる原因対処法
/plugin に Mod が表示されない信頼されていないフォルダ、--safe-mode、--bare、disableAllHooks のいずれかフォルダを信頼し、フラグを外して再起動する
hooks modules are turned off in this process と出るAnthropic がインストール済みの Mod をリモートで無効にしたローカルで直す箇所はない。2.1.289 と 2.1.290 で Mod が無効のままになる 2 つのケースが修正されたので、アップデートする
validate が「expected record, received undefined」で失敗するPATH 上で古い CLI が先に見つかっているclaude --version を実行して確認し、アップデートする
フックがエラー表示なしにスキップされる例外が出ている。たとえば $.plugin.name() を呼び出したが、これは関数ではなくプロパティclaude --debug で起動し、スキップの行を読む
ペインが開かない自動で開くペインは、幅が 144 カラム以上になるまで開かない。トランスクリプトの横にドッキングするのはフルスクリーンレイアウトのときだけで、/diff に覆われることもあるウィンドウを広げる、フルスクリーンレイアウトを試す、/diff を閉じる
ターミナルでは動くのに Desktop では動かないDesktop は独自のバージョンを同梱しており、ターミナル専用の要素もあるCode タブで /status を確認し、e.surface で分岐する
VS Code や claude -p で Mod の UI が出ないこれらのサーフェスは描画を行わないトランスクリプトに出す行や、コマンドが返すテキストで代用する
テストが「nothing beneath the plugins answers」で失敗するテストキットにはエンジンがない代役のフックを追加し、$ の呼び出しには { value } で応答する

Mod を公開する

.claude-plugin/ の中の、plugin.json の隣に marketplace.json を置きます。

{
  "name": "my-mods",
  "owner": { "name": "you" },
  "plugins": [{ "name": "safe-shell", "source": "./" }]
}

フォルダを GitHub に push すれば、誰でもターミナルのセッションから導入できます。

/plugin install safe-shell --marketplace you/safe-shell

Desktop アプリでは、ローカルセッションと SSH セッションで + → Plugins → Add plugin の順に進みます。リポジトリに claude-code-mod トピックを付けておくと、次に push してから数時間以内に、たいていコミュニティのカタログに載ります。

FAQ

Related Guides