# Claude Code Mods 怎么用：安装前先审查，再写第一个 mod

> Claude Code Mods 是插件里的 JS/TS 函数，2.1.287 起默认开启，能画窗格、改写工具调用，还能替你批准权限确认；装前先跑 validate、翻源码。

- URL: https://blog.laozhang.ai/zh/posts/claude-code-mods
- Published: 2026-10-06
- Updated: 2026-10-06
- Author: LaoZhang AI Team (https://blog.laozhang.ai/zh/about)
- Topic: Claude Code
- Tags: Claude Code, Mods, 插件, 开发者工具, Anthropic

---
Claude Code Mods 是 Anthropic 在 2026 年 10 月 1 日推出的功能：一段放在插件里的 JavaScript 或 TypeScript 代码，Claude Code 在工具调用、提交提示、绘制界面这些时刻调用它，它可以旁观、改写，或者干脆接管这件事。终端里的 Claude Code 从 2.1.287 起默认开启，不用改任何设置。想最快、最安全地用上第一个 mod，顺序是：确认版本 → 用 `claude plugin validate` 看它会调用什么 → 对启动程序、读写文件、联网的调用翻一遍源码 → 用 `claude --plugin-dir` 只在一次会话里试 → 满意了再从 marketplace 安装。

需要先记住的一条：mod 不在沙箱里，拥有和 Claude Code 一样的本机权限，还能在你看到确认框之前替你批准工具调用。用个人 Pro 或 Max 账号、机器上又没有组织下发的 managed settings 时，你在 `settings.json` 里写的 `deny` 规则也可能挡不住它，原因见下文的权限一节。

下面的命令输出来自 macOS 上的 Claude Code 2.1.288（Claude 桌面应用自带的那份 CLI，未登录账号），实际运行过的有：`claude plugin validate`、`claude plugin test`、`claude -p "/tally"`，以及从本地 marketplace 安装、停用、卸载官方示例 mod。没有运行的部分：没有开交互会话，所以窗格、提示框上方的横条、spinner 后缀、`/plugin` 里的 `mods active` 行、热重载和 Claude 写 mod 时的批准提示都没有亲眼看到；桌面端 Code 标签页、VS Code 扩展、Windows、Team/Enterprise 账号下的内置防护和任何社区 mod 也都没测。这些部分按 [Claude Code 官方中文文档《Mods 概览》](https://code.claude.com/docs/zh-CN/plugins/mods/overview)描述，文中会标明。

## Claude Code mod 是什么：跑在 Claude Code 进程里的插件函数

mod 是一种插件。普通插件可以带 skill、斜杠命令、子代理、MCP server；插件里只要有一个 `hooks/hooks.json` 用 `modules` 键指向一个 JS/TS 文件，这个插件就是 mod。Claude Code 会在自己的进程里调用这个文件注册的函数，所以 mod 不是"另一套插件系统"，而是插件能装的又一种东西，安装、更新、停用都走原来的 `/plugin`。

文档里有两种"hook"，容易混：

- **settings hook**：写在 `settings.json` 里、在生命周期事件上运行的 shell 命令、HTTP 请求或提示，就是以前大家说的 Claude Code Hooks。它没有被废弃，照常工作。
- **mod 里的 hook**：mod 代码里用 `on('tool.call', ...)` 这类写法注册的事件处理函数。

[Anthropic 的发布公告](https://claude.com/blog/claude-code-mods)列出的能力包括：在提示送到模型之前改写它；拦截、改写或重试工具调用；批准或拒绝权限请求；从工具输出里抹掉密钥；修改或替换界面元素；加按钮和输入框。

另外，这里的 mod 和 Minecraft 之类的游戏 mod 无关；claudemod.com 是另一个打包 hooks、命令和 CLAUDE.md 配置的网站，也不是这个功能。

## 需不需要 mod：先对照 settings hook、statusline、skill 和 MCP

settings hook、statusline、skill、MCP server 都是从 Claude Code 外部起作用：跑一个脚本，或者给 Claude 一段文字、一组工具。mod 跑在 Claude Code 内部，所以只有它能画可交互的界面、改 Claude Code 自己画的东西、在工具调用中途插手。反过来说，你的想法不涉及这三件事，多半不需要 mod。

| 你想做的事 | 用什么 | 说明 |
| --- | --- | --- |
| 用现成脚本拦截、放行或记录某个事件 | settings hook | 能改工具调用的参数和结果，不能画界面 |
| 在底部显示一行状态信息 | statusline 脚本 | 一个读 stdin、写 stdout 的本地命令 |
| 总在聊天里粘贴同一段说明 | skill | 一个 `SKILL.md` 文件 |
| 让 Claude 连到外部系统 | MCP server | 任何语言写的外部进程 |
| 在对话旁开一个窗格，或在提示框上方放一条带按钮、输入框的横条（官方中文文档称“带状区域”） | mod | 只有终端和桌面端能看到 |
| 改 Claude Code 自己画的 spinner、工具调用行、提问对话框 | mod | 权限确认框除外，任何 mod 都改不了 |
| 加一个 `/命令`，立刻运行你的函数、不经过 Claude 回合，Claude 干活时也能用 | mod | 命令本身不经过模型 |
| 在工具调用中途插手：先暂停问你、不运行工具直接给结果、把某次请求改发给另一个模型 | mod | 同一文件里的 hook 可以共享变量 |

表格依据[Claude Code《Mods 概览》里 mod、settings hook、skill 与 MCP 的对比](https://code.claude.com/docs/zh-CN/plugins/mods/overview#compare-mods-settings-hooks-skills-and-mcp-servers)。四者可以装在同一个插件里，并不互斥。settings hook、skill 和斜杠命令之间怎么选，见 [Claude Code Hooks、Skills 与斜杠命令：区别、选型和最小配置](https://blog.laozhang.ai/zh/posts/claude-code-hooks-slash-commands-skills)；只想要底部一行信息，[Claude Code Statusline 配置](https://blog.laozhang.ai/zh/posts/claude-code-statusline)更直接。

## 你的环境能不能用 mod：版本 2.1.287 起，以及一条检查命令

终端用户需要 Claude Code 2.1.287 或更新版本，用 `claude --version` 查看；旧版本先[更新 Claude Code](https://blog.laozhang.ai/zh/posts/claude-code-install)。桌面应用自带一份 Claude Code，从 2.1.286 起支持 mod，在 Code 标签页的本地会话里输入 `/status`，看 **Claude Code** 那一行的版本号。早期体验时设过 `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS` 的，可以删掉：2.1.287 起这个变量被忽略，设成 `0` 也关不掉 mod。

版本够了，再在一个不含 mod 的空目录里跑 `claude plugin test`。它不需要会话，也不需要登录。在 2.1.288 上的输出是：

```text
claude plugin test: .../empty: no hooks module to load; there is no hooks/hooks.json naming one in "modules"
```

按[Claude Code 官方文档《排查 mod 问题》](https://code.claude.com/docs/zh-CN/plugins/mods/troubleshoot#check-whether-mods-can-load)，消息里的关键词对应三种状态：

- `no hooks module to load`：可以加载 mod，只是这个目录里没有可测的 mod。
- `hooks modules are turned off here`：有设置挡住了 mod，可能是你自己设了 `disableAllHooks`，也可能是组织策略。
- `hooks modules are turned off in this process`：Anthropic 远程关闭了已安装的 mod，本机任何设置都打不开。

组织管理员还可以设 `allowManagedModsOnly`，只放行组织自己的 mod，这条命令查不出来，只会表现为你装的 mod 不加载。

还要看你在哪里用 Claude Code。mod 的 hook 在大多数地方都会运行，但画出来的界面只在终端和桌面端可见：

| 使用方式 | mod 的 hook 是否运行 | mod 画的界面能否看到 |
| --- | --- | --- |
| 终端里的 `claude`，含编辑器内置终端和 JetBrains 插件 | 运行 | 能 |
| 桌面应用 Code 标签页（非 WSL 会话） | 运行 | 能，标为仅限终端的元素除外 |
| 桌面应用里的 WSL 会话 | 不运行，WSL 会话不支持插件 | 不能 |
| VS Code 扩展的聊天面板 | 运行 | 不能 |
| `claude -p` 和 Agent SDK | 运行 | 不能 |
| 从 claude.ai 或手机 App 用 Remote Control | 在你本机的会话里运行 | 显示在本机那个终端里 |
| 云端会话 | 插件带到云端时运行 | 不能 |

这张表来自[Claude Code《Mods 概览》里 mod 在哪里运行的说明](https://code.claude.com/docs/zh-CN/plugins/mods/overview#where-mods-run)，上面各行都没有逐一实测。只在 VS Code 聊天面板里用 Claude Code 的话，画窗格、画横条一类的 mod 对你没有意义，改写工具调用一类的仍然有效。

## mod 能碰到什么：它可以替你批准 ask 确认，deny 规则未必挡得住

mod 加载后，按[Claude Code《Mods 概览》里 mod 能接触到什么的说明](https://code.claude.com/docs/zh-CN/plugins/mods/overview#what-a-mod-can-reach)，它能做到：

- 以你的身份读写你账号能碰到的任何文件、启动程序、发网络请求；
- 读环境变量和设置文件，包括放在里面的 API key；
- 看到你发的每一条提示和 Claude 的每一次工具调用；
- 改写提示和工具调用，像你亲手输入一样提交提示，给你的另一个会话发消息；
- 在你被询问之前批准一次工具调用；
- 用你的套餐或 API key 调用模型，花你的用量。

几条和权限设置相关的事实，决定了你在 `settings.json` 里配的规则还算不算数：

1. **ask 规则可以被绕过**：带 `tool.check` hook 的 mod 可以批准一个按 `ask` 规则本该弹确认的调用，也可以批准一个被非组织托管的 `PreToolUse` settings hook 拦下的调用。在[自动模式](https://blog.laozhang.ai/zh/posts/claude-code-auto-mode)下，mod 批准的调用不再经过分类器检查。
2. **deny 规则只在内置防护加载时才是最终的**：内置防护 mod 叫 `cc-plugin-sec-default`，它加载时，你的 mod 不能批准被 `deny` 规则拒绝的调用。它只在两种情况下加载：机器上有组织托管的 managed settings，或者你用 Team、Enterprise 套餐登录。用 API key、Amazon Bedrock、Google Cloud Agent Platform、Microsoft Foundry 认证的，只有机器上有 managed settings 才会加载。按这两个条件推断，用个人 Pro 或 Max 账号登录、机器上又没有 managed settings 时，没有这道防护，mod 可以批准被 `deny` 的调用。
3. **即使防护加载了，deny 也管不到 mod 自己的读写和进程**：deny 规则约束的是 Claude 的工具调用。设了 `Read(.env)` 拒绝，mod 仍然可以用 `$.fs.read` 读 `.env`，或者启动一个去读它的程序。
4. **沙箱不包住 mod**：开了沙箱，被隔离的是 Claude 运行的 Bash 命令；mod 启动的进程在沙箱外。组织的网络策略约束 `$.http.fetch`，但约束不到 mod 用 `$.process.run` 启动的程序。
5. **权限确认框本身改不了**：mod 能改 Claude Code 大部分界面，唯独改不了权限确认框显示的内容。它能做的是在确认框出现之前就替你决定。

![装了 mod 之后各项权限设置的效果：ask 规则可被 mod 批准，deny 规则只在内置防护加载时才是最终的，deny 和沙箱都管不到 mod 自己的读写和进程，权限确认框内容改不了](https://blog.laozhang.ai/posts/zh/claude-code-mods/img/mod-permission-rules.webp)

这些细节在 [Claude Code 官方文档《为您的组织管理 mods》](https://code.claude.com/docs/zh-CN/plugins/mods/admin#know-what-happens-by-default)里。换句话说，装一个会用 `tool.check` 的 mod，效果接近把一部分权限判断交给了它的作者；如果你本来就在考虑跳过权限确认，先看 [--dangerously-skip-permissions 的禁用场景和更安全替代](https://blog.laozhang.ai/zh/posts/claude-code-dangerously-skip-permissions)，两者都会让一部分工具调用不经你确认就执行。

## 装 mod 之前怎么审查：validate 看能力，源码看实际行为

审查分两步。第一步不运行代码：把插件文件拿到本地（比如 `git clone`），对插件目录运行：

```bash
claude plugin validate ./some-mod
```

它做的静态分析和 Claude Code 加载 mod 时做的一样。输出里的 `hooks:` 行列出 mod 处理哪些事件（花括号里是过滤条件），`calls:` 行列出它调用了哪些 mods API。mod 想碰文件、进程、网络、模型，只能通过这套 API；Claude Code 读不懂的写法直接拒绝加载，所以 `calls:` 行是这段 mod 代码的完整能力清单。它不涵盖同一插件里的 settings hook 脚本和 MCP server，那些要另外看。命令还有 `--json` 和 `--strict` 两个参数。

`calls:` 里出现下面这些时要停下来看：

| `calls:` 里出现 | 意味着 | 下一步 |
| --- | --- | --- |
| `$.fs.read`、`$.fs.write` | 读写你能碰到的任何文件 | 翻源码看读写哪些路径 |
| `$.process.run`、`$.process.spawn` | 以你的身份启动程序 | 翻源码看启动的是什么程序、带什么参数 |
| `$.http.fetch` | 发网络请求 | 翻源码看发到哪里、带上了什么 |
| `$.env.get`、`$.settings.read` | 读环境变量和设置，里面可能有 API key | 先看 `env reads:` 行列出的变量名，涉及 key 再翻源码 |
| `$.env.set` | 改环境变量，影响之后启动的命令和 MCP server | 看 `env writes:` 行，再翻源码 |
| `$.model.complete` | 用你的套餐或 API key 调模型 | 看调用时机和发送的内容 |
| `$.prompt.submit` | 提交提示，可以当成你说的话发出去 | 翻源码看提交什么 |
| `$.session.send` | 给另一个会话或子代理里的 Claude 发消息 | 翻源码看发什么 |
| `$.mcp.call` | 调用已连接 MCP server 的工具，受会话权限规则约束 | 看调用哪个工具 |

`hooks:` 行也有几项值得注意：`tool.call` 和 `prompt.submit` 表示它能看到并改写每一次工具调用和每一条提示；`session.append` 表示它能在对话记录保存前改写每一行；`ui.render{component=AskUserQuestion}` 表示它能重画 Claude 向你提问的对话框；`tool.check` 就是上一节说的，能在确认框出现前批准或拒绝调用。

对 Anthropic 的三个官方示例 mod 实际跑 validate（[claude-code-playground 仓库的 mods 目录](https://github.com/anthropics/claude-code-playground/tree/main/claude-code/mods)，2026 年 10 月 1 日的提交 569c5283），三个都通过，输出的关键行如下：

```text
token-weather（在提示框上方显示上下文窗口的“天气预报”）
  ❯ ./token-weather.mjs hooks: session.start, turn.complete, ui.render{component=AbovePrompt}
  ❯ ./token-weather.mjs calls: $.session.usage (via takeReading), $.ui.invalidate (via takeReading), $.ui.resolve

blast-radius（拦住高风险 shell 命令，显示影响范围，给出继续/取消按钮）
  ❯ ./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

replay-theater（加一个 /replay 命令，逐步回放上一回合的文件改动）
  ❯ ./replay-theater.mjs hooks: session.start, command.run{command=replay}, tool.call, turn.start, turn.complete, ui.render{component=AbovePrompt}, ui.render{component=Pane}, ui.close
  ❯ ./replay-theater.mjs calls: $.clock.sleep (via openReplay), $.command.register, $.fs.exists (via stepsFor), $.fs.read (via stepsFor), $.session.cwd (via stepsFor), $.ui.close (via replayView), $.ui.invalidate, $.ui.open (via openReplay), $.ui.resolve
```

三者都没有调用 `$.http.fetch`、`$.env.get` 或 `$.model.complete`，也就是不联网、不读环境变量、不花模型用量。token-weather 只读会话用量、画界面，风险最低。replay-theater 会读文件，`(via stepsFor)` 告诉你读文件发生在哪个函数里，要确认读的是哪些文件，就去看 `stepsFor`。

第二步是翻源码，blast-radius 是个好例子。validate 只说它会"启动程序"，没说启动什么。翻 `hooks/blast-radius.mjs` 才知道，它启动的是这几样：

```js
// 节选自 hooks/blast-radius.mjs
$.process.run(["sleep", POLL_SECONDS], { timeoutMs: 5000 })        // 等你点继续或取消时的轮询
$.process.run(["bash", "-c", CD_SCRIPT, "blast-radius", dir], ...)  // 解析 cd 的目标目录
$.process.run(["bash", "-c", RM_SCRIPT, "blast-radius", ...risk.targets], { cwd, timeoutMs: 15000 })  // 统计 rm 会删掉多少文件和字节
```

源码注释解释了为什么用 `sleep`：一个 hook 自己只有 10 秒执行时间，待在 `$` 调用里的时间不算。它会拦下的命令包括带 `-r` 或 `-f` 的 `rm`、`git reset --hard`、`git push --force`（以及 `-f`、`--force-with-lease`、`+ref` 写法）、`alembic upgrade`、`rails db:migrate`、`prisma migrate`、`manage.py migrate`。按源码，这些进程只是等待用的 `sleep` 和预演统计用的脚本；被拦下的命令要等你点继续才会照常执行。这层信息 validate 给不出来。

把两步合起来，装前检查清单是：

1. 作者和 marketplace 是否可信，README 写没写测过的 Claude Code 版本。
2. `claude plugin validate` 能否通过，`hooks:` 里有没有 `tool.check`、`prompt.submit`、`session.append`。
3. `calls:` 里有没有上表那些调用；有就翻源码，看路径、程序、地址和发送的内容。
4. 有 `tool.check` 时，想清楚它会批准什么，以及你的 `deny` 规则在本机是否真的生效。
5. 先用 `--plugin-dir` 在一次会话里试，确认行为符合 README，再安装。

![装 mod 前的四步：跑 claude plugin validate 看能力清单，翻源码看实际行为，用 --plugin-dir 试一次会话，满意后再从 marketplace 安装](https://blog.laozhang.ai/posts/zh/claude-code-mods/img/mod-review-steps.webp)

## 先用 --plugin-dir 试一次会话，再从 marketplace 安装

只试一次、不安装，用 `--plugin-dir` 指向 mod 目录：

```bash
git clone --depth 1 https://github.com/anthropics/claude-code-playground
claude --plugin-dir ./claude-code-playground/claude-code/mods/token-weather
```

这个参数只对本次会话生效，还会在你保存文件时热重载 mod 的代码；要同时加载几个，就重复写几次。没法传命令行参数的应用，可以用环境变量 `CLAUDE_CODE_PLUGIN_DIRS` 达到同样效果。加载后在会话里运行 `/plugin`，按文档，标签页下方会有一行灰字，比如 `1 mod active · first-mod`，列出本次会话加载的 mod（不含内置 mod）。

想长期用，就把它装成插件。mod 都从 marketplace 安装：会话里用 `/plugin install 插件名@市场名`，shell 里用 `claude plugin install`。marketplace 的来源可以是 GitHub 的 `owner/repo`、任意 git 地址、以 `./` 或 `../` 开头的本地路径，或一个托管的 `marketplace.json` 地址。下面是把示例仓库的 mods 目录加成本地 marketplace、安装 token-weather、再停用和卸载的完整过程，在 2.1.288 上的真实输出（配置目录做了隔离，没有动真实的 `~/.claude`）：

```text
$ cd claude-code-playground/claude-code/mods
$ claude plugin marketplace add ./
✔ Successfully added marketplace: claude-code-playground-mods (declared in user settings)

$ claude plugin install token-weather@claude-code-playground-mods --scope user
✔ Successfully installed plugin: token-weather@claude-code-playground-mods (scope: user)

$ claude plugin list
Installed plugins:
  ❯ token-weather@claude-code-playground-mods
    Version: 0.1.0
    Scope: user
    Status: ✔ enabled

$ claude plugin disable token-weather@claude-code-playground-mods
✔ Successfully disabled plugin: token-weather (scope: user)

$ claude plugin uninstall token-weather@claude-code-playground-mods
✔ Successfully uninstalled plugin: token-weather (scope: user)
```

几点补充：

- 安装范围默认是 user，也可以用 `--scope project` 或 `--scope local`，详见 [Claude Code 插件安装文档](https://code.claude.com/docs/zh-CN/plugins/install)。
- 会话开着时在 shell 里装或更新了 mod，要在会话里跑 `/reload-plugins` 才会加载，否则下次启动才生效。
- 从本地克隆添加的 marketplace 指向那个目录，克隆被移动或删除后，mod 就不再加载。
- 在会话里也可以运行 `/plugin`，按 Tab 切到 **Installed** 标签页，停用、更新或卸载。

## 关掉 mod：单个停用、--safe-mode 和 disableAllHooks

按想停多少、停多久，有三种做法：

- **只停一个**：在 `/plugin` 的 **Installed** 标签页停用或卸载它所在的插件，或在 shell 里用 `claude plugin disable` / `claude plugin uninstall`。
- **本次会话停掉所有已安装的 mod**：用 `claude --safe-mode` 启动，它同时会关掉你的其他自定义项。怀疑某个 mod 惹了麻烦时，先用这个排除。
- **所有会话都停掉你装的 mod**：在 `~/.claude/settings.json` 里设 `"disableAllHooks": true`。代价是你的 settings hook 和自定义 statusline 也一起停；组织托管的部分照常运行。

`disableAllHooks` 和组织的 `allowManagedModsOnly` 只停 mod 代码，插件里的 skill、命令、子代理和 MCP server 仍会加载。

Claude Code 自己的一部分功能就是内置 mod，在 `/plugin` 的 **Installed** 标签页 **Built-in** 下能看到。它们不能更新或卸载，`disableAllHooks`、`--safe-mode` 和 `--bare` 也停不掉，要关只能在 `/plugin` 里逐个停用（`cc-plugin-sec-default` 除外）：

| `/plugin` 里的名字 | 作用 |
| --- | --- |
| `cc-plugin-agents-md` | 把 `AGENTS.md` 当作项目说明加载 |
| `cc-plugin-diff` | 接管 `/diff` 并画出它的窗格 |
| `cc-plugin-plugin-authoring` | 给 Claude 提供写 mod 用的 `plugin-authoring` skill，本身不含 mod 代码 |
| `cc-plugin-sec-default` | 上一节说的内置防护，用户关不掉 |
| `cc-plugin-telemetry` | 发送 Claude Code 及内置 mod 的分析记录 |
| `cc-plugin-you-should-know` | 一个旁观代理，长任务中发现你可能漏看的事就在提示框上方提醒；默认关闭 |

最后一个要手动开启：`/plugin enable cc-plugin-you-should-know@builtin`。其中 diff、agents-md、sec-default、telemetry 的源码公开在 [Claude Code 仓库的 mods 目录](https://github.com/anthropics/claude-code/tree/main/mods)，想看一个完整 mod 怎么写，可以从这里读起。

## 让 Claude 写一个 mod：文件在 ~/.claude/dev-mods，记得拷出来

这是最省事的做法，需要一个已登录的交互会话，下面的流程按 [Claude Code 官方文档《创建一个 mod》](https://code.claude.com/docs/zh-CN/plugins/mods/create#ask-claude-for-a-mod)整理，没有实际跑过。

1. 在会话里直接描述，比如 `make a mod that shows the current git branch above the prompt`（用中文描述也可以）。Claude 会用内置的 `plugin-authoring` skill，你也可以先运行 `/plugin-authoring` 手动加载。
2. 文件写在 `~/.claude/dev-mods/` 下以会话 ID 命名的目录里。`~/.claude` 是受保护路径，在 `default` 和 `acceptEdits` 权限模式下，每个文件都要你批准。
3. 写第一个文件时，Claude Code 会问是否为本次会话开启热重载：选 **Enable for this session**，mod 在回合结束时加载，之后每次改动都会重载；选 **Not now**，文件留着，下次启动这个会话时再加载。
4. 运行 `/plugin` 到 **Installed** 标签页，确认 mod 已列出。效果不对就告诉 Claude 改哪里。

要注意的是，Claude 写的 mod 只在写它的那个会话里加载，而且这个会话的 mods 目录超过 `cleanupPeriodDays` 设置的天数就会被删掉。想留下来，把它的目录拷到自己的位置（比如 `~/mods/git-branch`），以后用 `claude --plugin-dir ~/mods/git-branch` 加载，或者放进一个 marketplace。

在 `claude -p`、`dontAsk` 模式、尚未信任的目录，或者 mod 被关掉的会话里，Claude 写的 mod 不会加载，因为没人能批准它，或者环境不允许。

## 自己写第一个 mod：三个文件、validate、test 和 claude -p

自己写一遍，最能看懂 mod 的结构。不需要 Node.js、打包工具或构建步骤，Claude Code 直接加载 `.js` 和 `.ts`。下面是官方教程里的 `first-mod`：统计 Claude 的工具调用次数，显示在 spinner 后面，并加一个 `/tally` 命令打印次数。

```text
first-mod/
├── .claude-plugin/
│   └── plugin.json
└── hooks/
    ├── hooks.json
    └── register.js
```

`first-mod/.claude-plugin/plugin.json` 是插件清单：

```json
{
  "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" }
}
```

`first-mod/hooks/hooks.json` 里的 `modules` 键指向代码文件，有了它，这个插件才是 mod：

```json
{
  "description": "The first-mod hooks module",
  "modules": ["./register.js"]
}
```

`first-mod/hooks/register.js` 导出 `register(on)`，每调用一次 `on` 就注册一个 hook。每个 hook 都收到三个参数：`$` 是 mods API，`e` 是事件数据，`next` 把事件交给后面的 mod 和 Claude Code 自己的默认行为。

```js
// 下面几个 hook 共享的计数
let calls = 0

export function register(on) {
  // 会话开始时注册 /tally 命令，然后照常继续
  on('session.start', async ($, e, next) => {
    await $.command.register({
      name: 'tally',
      description: 'Show how many tool calls Claude has made',
    })
    return next(e)
  })

  // Claude 每次要用工具时计数，并请求重绘界面
  on('tool.call', async ($, e, next) => {
    calls += 1
    $.ui.invalidate('ui.render')
    return next(e)
  })

  // 只在输入 /tally 时运行，直接返回结果，不调用 next
  on('command.run', { command: 'tally' }, async () => {
    return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
  })

  // 每次绘制 spinner 时，在原来的文字后面加上计数
  on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
    return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
  })
}
```

四个 hook 正好展示了三种处理方式：`session.start` 和 `tool.call` 做完自己的事后返回 `next(e)`，属于旁观；`command.run` 自己给出结果、从不调用 `next`，属于接管；`ui.render` 把改过的事件传给 `next`，属于改写。

### 用 validate、test 和 claude -p 检查 first-mod

写完先跑 validate。2.1.288 上的输出：

```text
$ claude plugin validate ./first-mod
Validating plugin manifest: .../first-mod/.claude-plugin/plugin.json
Validating hooks: .../first-mod/hooks/hooks.json
  ❯ ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner}
  ❯ ./register.js calls: $.command.register, $.ui.invalidate
✔ Validation passed
```

再加一个测试文件 `first-mod/tests/first-mod.test.ts`（官方教程原文）：

```ts
import { expect, test } from 'claude-code/testing'

test('/tally reports the tool calls the mod has seen', async ($, on) => {
  // 代替 Claude Code 回答每个工具调用，不真的运行工具
  on('tool.call', () => ({ result: 'ok' }))

  await $.tool.call({ tool: 'Bash', command: 'ls' })
  await $.tool.call({ tool: 'Read', file_path: 'README.md' })

  const answer = await $.command.run({ command: 'tally', args: '' })
  expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')
})
```

在 `first-mod` 目录里运行 `claude plugin test`，不需要会话、登录和网络：

```text
tests/first-mod.test.ts:
(pass) /tally reports the tool calls the mod has seen [64.48ms]
 1 pass
 0 fail
Ran 1 test across 1 file. [0.34s]
```

耗时每次都会不同。最后用非交互模式跑一次命令：

```text
$ claude -p "/tally" --plugin-dir ./first-mod
first-mod: Claude has made 0 tool calls since this mod loaded
```

这条在未登录的 CLI 上也能返回结果，因为 `command.run` 直接作答，没有经过模型。Claude Code 会在命令输出前加上插件名。交互会话里 spinner 后面出现的 `Thinking · tool calls: 2…` 和保存后热重载的效果，在官方教程的录屏里能看到，本机没有在交互会话里验证。

### 静态分析的写法规则和两条真实报错

validate 读不懂的写法，Claude Code 会拒绝加载，所以写 mod 有几条硬规则：

- 每个 API 调用写全：`$`、命名空间、方法，比如 `$.store.get('notes')`。不要把 `$` 或它的命名空间赋给变量、解构或用计算出来的名字取下标，`const ui = $.ui` 会报 `$.ui is used as a value`。可以把 `$` 传给同一文件顶层声明的函数，`calls:` 行会显示成 `(via 函数名)`。
- `on` 的事件名必须是字符串字面量，不能用变量或循环。
- 只能用顶部的 `import` 声明导入插件目录内的文件，唯一允许的裸导入是 `claude-code`；不能用动态 `import()`，也不能用 `require`。
- 插件名看起来像 Anthropic 官方的（比如以 `claude-` 开头），validate 会失败。

从 `--plugin-dir` 加载时，Claude Code 会把当前版本的类型声明写进 mod 目录的 `.claude-plugin/types/`。事件和方法会随版本变化，和文档对不上时以这些类型文件为准。

故意写错两处，2.1.288 给出的报错是这样的。事件名拼成 `'tool.calls'`：

```text
✘ Found 1 error:
  ❯ modules../register.js: bad-mod: .../hooks/register.js:7: "tool.calls" is not an event; $ is always spelled $.noun.event(...) at the call site, on is always on("<event>", hook), and next.to always next.to(e, "<tier>")
✘ Validation failed
```

`hooks.json` 里把 `modules` 写成 `module`，又没有 `hooks` 键：

```text
✘ Found 1 error:
  ❯ root: hooks.json must have `hooks` (the hook matchers) or `modules` (hooks modules), or both
✘ Validation failed
```

官方排错文档还提到另一种情况：`modules` 键缺失或拼错时，validate 会通过但不输出 `hooks:` 行。推测那是 `hooks.json` 里还有 `hooks` 键的情形。总之，validate 通过却没有 `hooks:` 行，同样说明 `modules` 没写对。

写好的 mod 想给别人用：发目录或 `.zip`；放进自己的 marketplace（比如一个私有仓库）；组织管理员通过 managed settings 统一安装；或者公开仓库、提交到 Anthropic 的插件目录。README 里写清你测试过的 Claude Code 版本。继续开发时用 `--plugin-dir` 指向源目录，因为已安装的副本按版本号缓存，不升版本重装，改动到不了已安装的那份。

## mod 装了没反应：按这个顺序排查

mod 的模块或某个 hook 出错时，Claude Code 会跳过它、让会话继续，所以坏掉的 mod 看起来就像什么都没做。按[Claude Code 官方文档《排查 mod 问题》](https://code.claude.com/docs/zh-CN/plugins/mods/troubleshoot)，可以这样查：

1. **先跑 `claude plugin validate`**：事件名拼错、清单有问题、模块读不懂，不开会话就能查出来。
2. **找那一行说明**：Claude Code 跳过 mod 时会写一行带 mod 名字的说明。用 `--plugin-dir` 或开了热重载的会话，它以灰字出现在对话里；从 marketplace 安装的 mod，只写进调试日志，要用 `claude --debug` 启动才看得到；`claude -p` 写到 stderr。
3. **看拒绝原因**：以 `hooks module 插件名 not loaded:` 开头，冒号后是原因，比如 `disableAllHooks in managed settings`（组织关了插件 hook）、`only managed plugins and built-in plugins run`（设了 `allowManagedHooksOnly`，或在非 managed 设置里设了 `disableAllHooks`）、带 `(--bare)` 的（用 `--bare` 启动）、`another plugin of that name loads first`（重名插件先加载了）。
4. **新目录里一个 mod 都不加载**：还没接受这个目录的信任提示。先在该目录运行 `claude`，接受信任提示。
5. **所有已安装插件都不加载**：多半是用 `--safe-mode` 启动的。
6. **组织账号下的防护消息**：`mods are limited to your organization's by policy (allowManagedModsOnly)` 表示组织只放行自己的 mod；`tried to lift a deny rule in your settings` 表示你的 mod 想批准一个被 `deny` 拒绝的调用，调用仍被拒绝。
7. **超出时间或大小上限**：一个 hook 每次事件自己的执行时间上限是 10 秒（`prompt.edit` 是 50 毫秒），`$.process.run` 默认 30 秒、最长 10 分钟，`$.fs` 读写单个文件上限 4 MiB，`claude plugin test` 里单个测试默认 5 秒。hook 超出自己的时间上限会被跳过。这些上限出自 [Claude Code 官方文档《mod 参考》的 Limits 一节](https://code.claude.com/docs/zh-CN/plugins/mods/reference#limits)，可能随版本调整。

## Claude Code Mods 的收费、去哪找和分享

### Claude Code Mods 要额外付费吗？

公告和文档里没有为 mod 单独收费的说法，它随 Claude Code 版本一起提供。要留意的是用量：调用了 `$.model.complete` 的 mod 会用你的套餐或 API key 调模型，validate 的 `calls:` 行能看出来。只响应 `/命令`、不调用 `$.model.complete` 的 mod（像上面的 `/tally`）直接作答，不经过模型。

### 去哪找别人写好的 Claude Code mod？

起点是 Anthropic 的三个示例（token-weather、blast-radius、replay-theater），仓库说明它们按原样分享、不提供支持。作者也可以把 mod 提交到 Anthropic 的插件目录。第三方的 [awesome-claude-code-mods](https://github.com/karanb192/awesome-claude-code-mods) 汇总了从 GitHub 扫描到的公开 mod，并附上每个 mod 的 validate 结果，截至 2026 年 10 月 6 日列出 2,685 个；它自己说明是独立扫描、不是官方目录，验证通过也不代表行为安全。不管从哪里找到，都按上面的审查清单过一遍。

### 写好的 Claude Code mod 怎么分享给同事？

最简单是把目录或 `.zip` 发过去，对方用 `claude --plugin-dir` 加载；团队长期用，就建一个私有仓库当 marketplace，大家 `claude plugin marketplace add` 后安装；整个组织统一下发，由管理员通过 managed settings 安装。和 skill、MCP 一起打包也可以，[Claude Code 最值得先用的 Skills](https://blog.laozhang.ai/zh/posts/claude-code-best-skills)和[最值得先加的 MCP](https://blog.laozhang.ai/zh/posts/claude-code-best-mcp-servers)可以帮你挑同一插件里的其他组件。

## 参考来源

本文引用的外部页面，按正文出现顺序排列。最后更新于 2026-10-06。

- [Claude Code 官方中文文档《Mods 概览》](https://code.claude.com/docs/zh-CN/plugins/mods/overview) (code.claude.com)
- [Anthropic 的发布公告](https://claude.com/blog/claude-code-mods) (claude.com)
- [Claude Code 官方文档《排查 mod 问题》](https://code.claude.com/docs/zh-CN/plugins/mods/troubleshoot) (code.claude.com)
- [Claude Code 官方文档《为您的组织管理 mods》](https://code.claude.com/docs/zh-CN/plugins/mods/admin) (code.claude.com)
- [claude-code-playground 仓库的 mods 目录](https://github.com/anthropics/claude-code-playground/tree/main/claude-code/mods) (github.com)
- [Claude Code 插件安装文档](https://code.claude.com/docs/zh-CN/plugins/install) (code.claude.com)
- [Claude Code 仓库的 mods 目录](https://github.com/anthropics/claude-code/tree/main/mods) (github.com)
- [Claude Code 官方文档《创建一个 mod》](https://code.claude.com/docs/zh-CN/plugins/mods/create) (code.claude.com)
- [Claude Code 官方文档《mod 参考》的 Limits 一节](https://code.claude.com/docs/zh-CN/plugins/mods/reference) (code.claude.com)
- [awesome-claude-code-mods](https://github.com/karanb192/awesome-claude-code-mods) (github.com)
