1. 从 claude-plugins-official 这个仓库说起
第一次看到claude-plugins-official这个名字,很多人会下意识以为它是 Anthropic 官方发布的一个插件市场,点进去就能像逛应用商店一样一键装插件。实际接触下来你会发现,它更像是一个官方维护的插件规范与示例集合——里面放的是插件该怎么写、目录怎么组织、清单文件长什么样、有哪些官方认可的扩展点。换句话说,它给的是"标准答案的模板",而不是"装满成品的货架"。
这个区别非常关键。因为 Claude Code 的插件生态和传统 IDE 插件(比如 VS Code 那种点一下 Install 就完事的)完全不是一回事。Claude Code 的插件本质上是一组约定好的文件结构 + 配置声明,它把你的自定义命令、子代理(subagent)、钩子(hook)、MCP 服务配置、技能(skill)这些东西打包成一个可被 Claude Code 识别的单元。claude-plugins-official这个仓库的价值,就在于它用官方视角把这些约定固化下来,让你不用去猜"到底该放哪个目录、清单里该写哪些字段"。
我最初踩的坑就是:以为装个插件跟装个 npm 包一样,npm install完就生效。结果折腾半天发现,Claude Code 的插件加载有一套自己的发现机制,路径不对、清单字段写错、或者版本声明不匹配,它就直接静默跳过,连个像样的报错都不给你。这也是为什么热词里会冒出harness failed to load plugins这种让人头大的提示——它说的就是插件加载器在启动时没能成功激活某些条目。
所以这篇内容我打算把claude-plugins-official这个仓库拆开讲清楚:它到底定义了什么、插件是怎么被加载的、为什么会出现加载失败、以及怎么基于官方规范自己写一个能跑起来的插件。适合已经装好 Claude Code、想进一步做定制化的同学,也适合那些被harness failed to load plugins卡住、想搞明白背后机制的人。哪怕你只是想搞清楚"插件和 skill 到底啥关系",看完也能有个清晰的判断。
2. 插件机制的整体设计与思路拆解
2.1 为什么 Claude Code 要用"插件"这套抽象
要理解claude-plugins-official,得先理解 Claude Code 为什么要引入插件这个概念。早期的 Claude Code 定制化方式很零散:你想加个自定义命令,就去某个目录丢个 markdown 文件;你想加个钩子,就去改全局配置文件;你想接个外部工具,就去配 MCP。这些方式各自能用,但没法打包、没法分发、没法版本管理。团队里一个人配好了,想同步给其他人,只能靠"你把这几个文件拷过去,再改改路径"。
插件机制解决的就是这个"打包与分发"的问题。它把原本散落在各处的定制内容,收敛到一个有明确边界的目录里,用一个清单文件(manifest)声明"我这个插件提供了什么"。这样一来,插件就可以被 Git 管理、被团队共享、被版本化。claude-plugins-official作为官方仓库,做的就是把这份清单的 schema 和目录约定标准化,让所有人写出来的插件都能被同一套加载器识别。
这里有个设计哲学值得说:Claude Code 的插件是声明式的,不是命令式的。你不需要写代码去"注册"一个命令,你只需要在约定位置放好文件、在清单里声明它。加载器启动时扫描目录、读取清单、按声明去挂载。这种设计的好处是加载过程可预测、可校验;坏处是——一旦你的声明和实际文件对不上,加载器不会帮你猜,直接跳过。这就是很多"插件没生效"问题的根源。
2.2 插件、Skill、MCP、Subagent 的关系梳理
热词里频繁出现claude code skill、claude code怎么手动装github上的skills,说明很多人把 skill 和 plugin 混为一谈。我梳理一下这几个概念在 Claude Code 里的层次关系,这个搞清楚了,后面写插件就不会迷路。
| 概念 | 本质 | 归属层级 | 典型用途 |
|---|---|---|---|
| Plugin | 打包与分发单元 | 最外层容器 | 把下面几样东西打包共享 |
| Skill | 一段可复用的能力描述 | 插件内或独立 | 教 Claude 怎么完成某类任务 |
| Subagent | 独立的子代理配置 | 插件内或独立 | 把某类任务隔离出去单独跑 |
| Hook | 生命周期回调 | 插件内或独立 | 在工具调用前后插入逻辑 |
| MCP Server | 外部工具/数据接入 | 插件内或独立 | 连接数据库、API、本地服务 |
| Command | 自定义斜杠命令 | 插件内或独立 | 快捷触发某个流程 |
关键点在于:Plugin 是容器,Skill/Subagent/Hook/MCP/Command 是内容。你可以单独放一个 skill 到用户目录让它生效,也可以把它塞进一个 plugin 里打包分发。claude-plugins-official定义的,就是这个容器该怎么造、内容该怎么摆。
我个人的经验是:如果只是自己用、临时试,直接放独立目录最快;如果要给团队用、要跨机器同步、要版本管理,那就老老实实按官方规范打成 plugin。别小看这个选择,我见过太多人图省事把一堆 skill 散着放,结果换台机器就全丢了,回头还得一个个重新配。
2.3 官方仓库的目录约定与清单结构
claude-plugins-official最核心的产出,是一套目录约定。虽然具体字段会随版本演进,但骨架是稳定的。一个符合规范的插件,大致长这样:
my-plugin/ ├── .claude-plugin/ │ └── plugin.json # 清单文件,声明插件元信息与提供的能力 ├── commands/ # 自定义斜杠命令 │ └── hello.md ├── agents/ # 子代理定义 │ └── reviewer.md ├── skills/ # 技能 │ └── my-skill/ │ └── SKILL.md ├── hooks/ # 钩子脚本与配置 │ └── hooks.json └── .mcp.json # MCP 服务配置(可选)清单文件plugin.json是加载器读取的入口,它至少要声明插件的名字、版本,以及它提供了哪些能力。这里有个容易翻车的点:清单里声明的每一项,都必须在对应目录里真实存在。你声明了commands/hello.md,但文件没放对位置,加载器扫描时找不到,就会记一条"未激活"。热词里那个harness failed to load plugins web boot: 2 entries did not activate,说的就是启动时有 2 个条目没能激活——大概率就是声明和实际文件对不上,或者路径写错了。
提示:清单文件里的路径通常是相对于插件根目录的,不要写成绝对路径,也不要用
~。跨平台时尤其注意路径分隔符,统一用正斜杠最稳。
3. 核心细节解析与实操要点
3.1 清单文件到底该写什么
清单文件是插件的"身份证 + 说明书"。它告诉加载器:我是谁、我提供什么、我依赖什么。虽然官方 schema 会更新,但核心字段就那么几类。我按实际写插件的经验,把最常打交道的字段列一下,并说明每个字段为什么重要。
{ "name": "my-team-plugin", "version": "1.0.0", "description": "团队内部常用的命令与技能集合", "author": "your-team", "commands": ["commands/hello.md"], "agents": ["agents/reviewer.md"], "skills": ["skills/my-skill"], "hooks": "hooks/hooks.json" }name和version是必填的,加载器靠它们做去重和版本判断。如果你装了两个同名插件,后加载的可能会覆盖前面的,或者直接被跳过——这也是"插件没生效"的常见原因之一。description虽然不影响加载,但在你列出已装插件时能帮你快速辨认,强烈建议写清楚。
commands、agents、skills这些数组字段,每一项都是一个路径。路径必须真实存在,这是硬性要求。我建议的做法是:先建好文件,再往清单里填路径,而不是反过来。反过来写最容易出现"声明了但文件没建"的情况,加载器一扫描就报未激活。
hooks字段指向一个钩子配置文件,这个文件里再声明具体在哪个生命周期触发哪个脚本。钩子的坑后面单独讲。
3.2 目录命名与文件放置的硬性规则
Claude Code 的插件加载器对目录名是有约定的,不是随便叫什么都行。commands/、agents/、skills/、hooks/这些是约定目录,加载器会去这些位置找对应类型的内容。你把命令文件放到skills/里,它就不会被当成命令加载。
我整理了一份常见目录与内容的对应关系,照着放基本不会错:
| 目录 | 放什么 | 文件格式 |
|---|---|---|
commands/ | 自定义斜杠命令 | Markdown |
agents/ | 子代理定义 | Markdown |
skills/ | 技能,每个技能一个子目录 | 子目录内含 SKILL.md |
hooks/ | 钩子配置与脚本 | JSON + 脚本文件 |
.claude-plugin/ | 清单文件 | plugin.json |
这里有个细节:skills/下面是每个技能一个子目录,子目录里放SKILL.md。不是直接把一堆 md 文件丢在skills/下。我第一次写的时候就犯了这个错,把my-skill.md直接放skills/里,结果加载器根本不认。后来才明白,技能是一个"目录级"的概念,因为一个技能可能附带参考文件、脚本、模板,需要独立目录来装。
注意:目录名大小写敏感。在 Linux 和 macOS 上
Commands/和commands/是两个不同的目录,加载器只认小写的约定名。Windows 上虽然不区分大小写,但为了跨平台一致,也统一用小写。
3.3 插件加载的完整流程
理解加载流程,是排查harness failed to load plugins的前提。加载器启动时大致做这几件事:
- 发现:扫描配置的插件目录(用户级、项目级、以及通过配置指定的路径),找出所有含
.claude-plugin/plugin.json的目录。 - 解析:读取每个
plugin.json,校验必填字段,解析出它声明了哪些能力。 - 校验:对每一项声明,去对应路径检查文件是否存在、格式是否合法。
- 挂载:把通过校验的能力注册到运行时,命令进命令表、技能进技能库、钩子进钩子链。
- 报告:把没能激活的条目汇总,输出类似
N entries did not activate的提示。
问题基本都出在第 3 步。加载器校验很严格,但报错很克制——它不会告诉你"你第 3 个命令文件路径写错了",只会告诉你"有 2 个条目没激活"。所以排查时你得自己对照清单和实际文件,一个个核对。
我常用的排查手法是:把清单里声明的路径逐条复制出来,在终端里ls一遍。哪条ls不到,问题就在哪。这个方法土,但极其有效,比盯着报错猜快得多。
4. 实操过程与核心环节实现
4.1 从零写一个最小可用插件
光讲概念没用,我带你从零写一个能跑起来的最小插件。这个插件提供一个自定义命令,功能是让 Claude 帮你生成一段规范的提交信息。麻雀虽小,但把清单、目录、命令文件三样都覆盖到了。
第一步,建目录结构:
mkdir -p my-first-plugin/.claude-plugin mkdir -p my-first-plugin/commands第二步,写清单文件my-first-plugin/.claude-plugin/plugin.json:
{ "name": "my-first-plugin", "version": "0.1.0", "description": "一个演示用的最小插件", "commands": ["commands/commit-msg.md"] }第三步,写命令文件my-first-plugin/commands/commit-msg.md。命令文件本质是一段给 Claude 的提示词,加上一些元信息。内容大致这样:
--- description: 根据当前改动生成规范的提交信息 --- 请查看当前工作区的改动,按照约定式提交(Conventional Commits)的格式, 生成一条简洁准确的提交信息。只输出提交信息本身,不要额外解释。第四步,把这个插件目录放到 Claude Code 能发现的位置。具体位置取决于你的配置,通常是用户级插件目录或项目级插件目录。放好之后重启 Claude Code,让它重新扫描。
第五步,验证。在 Claude Code 里输入斜杠,看命令列表里有没有commit-msg。有,说明加载成功;没有,就回到第 3 步的排查方法,逐条核对路径。
这个流程走一遍,你就把插件机制的骨架摸清了。后面加技能、加钩子、加 MCP,都是在这个骨架上扩展。
4.2 给插件加一个技能
技能和命令的区别在于:命令是你主动用斜杠触发的,技能是 Claude 在合适的时候自己判断要不要用。技能适合封装"某类任务的完整做法",比如"如何审查一段 SQL"、"如何写单元测试"。
给上面的插件加一个技能,先建目录:
mkdir -p my-first-plugin/skills/sql-review然后在skills/sql-review/SKILL.md里写技能描述。技能文件的关键是把触发条件和执行步骤写清楚,因为 Claude 要靠这些描述来判断什么时候该调用它:
--- name: sql-review description: 当用户提供 SQL 语句并希望审查其性能或正确性时使用 --- 审查 SQL 时,按以下顺序检查: 1. 是否有全表扫描风险,WHERE 条件是否命中索引 2. JOIN 的顺序与驱动表选择是否合理 3. 是否有隐式类型转换导致索引失效 4. 子查询能否改写为 JOIN 5. 是否缺少必要的 LIMIT 输出时按严重程度排序,每条给出问题、原因、修改建议。写完记得回到plugin.json,在skills数组里加上"skills/sql-review"。这一步最容易忘,忘了就等于技能没声明,加载器不会去挂载它。
4.3 钩子的接入与常见陷阱
钩子是插件里最容易出问题的部分,因为它涉及脚本执行。钩子配置通常长这样:
{ "hooks": { "PostToolUse": [ { "matcher": "Write", "hooks": [ { "type": "command", "command": "bash hooks/format.sh" } ] } ] } }这段配置的意思是:在Write工具使用之后,执行hooks/format.sh。钩子的价值在于自动化——比如每次写完文件自动格式化、自动跑 lint。
钩子的坑主要有三个。第一,脚本路径。command里的路径是相对于插件根目录还是当前工作目录,不同版本行为可能不同,最稳的做法是用绝对路径或者在脚本里自己处理路径。第二,脚本权限。在 Linux/macOS 上脚本得有可执行权限,chmod +x别忘了,否则钩子触发时直接失败。第三,脚本超时。钩子脚本如果卡住,会拖慢整个工具调用,所以脚本里别做重活,快速返回。
提示:调试钩子时,先在终端里手动跑一遍脚本,确认脚本本身没问题,再去排查钩子配置。把"脚本问题"和"配置问题"分开定位,能省一半时间。
4.4 把插件共享给团队
插件写好了,怎么让团队其他人用上?最直接的方式是把插件目录提交到 Git 仓库,其他人 clone 下来,放到自己的插件目录里。但这样有个问题:每个人的插件目录路径可能不一样,手动放容易出错。
更规范的做法是把插件做成一个独立的 Git 仓库,然后在项目里通过配置引用它。这样插件可以独立版本化,团队升级插件时拉一下就行,不用互相拷文件。claude-plugins-official本身就是这种模式的示范——它是一个仓库,你引用它、参考它,而不是把它整个拷进项目。
我个人的建议是:团队内部插件单独建一个仓库,按功能拆成多个插件目录,每个插件有自己的plugin.json和版本号。这样谁需要哪个就引哪个,不会互相干扰。
5. 常见问题与排查技巧实录
5.1 harness failed to load plugins 到底在说什么
这个报错是问得最多的。harness指的是 Claude Code 的运行时框架,failed to load plugins是说框架在加载插件阶段遇到了问题,后面跟的N entries did not activate是具体有多少个条目没能激活。
它不是说整个插件系统崩了,而是说某些条目被跳过了。可能的原因按出现频率排:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 命令不出现 | 清单未声明或路径错 | 核对 plugin.json 与文件 |
| 技能不触发 | SKILL.md 描述不清 | 检查 description 是否明确 |
| 钩子不执行 | 脚本无执行权限 | chmod +x后重试 |
| 整个插件不加载 | 清单 JSON 语法错 | 用 JSON 校验工具检查 |
| 部分条目未激活 | 声明了但文件不存在 | 逐条 ls 核对路径 |
我遇到最多的是清单 JSON 语法错误。JSON 不允许尾随逗号,不允许注释,一个多余的逗号就能让整个清单解析失败,进而整个插件不加载。写完清单后,丢进任意 JSON 校验器过一遍,能避免大量低级问题。
5.2 插件装了但命令不生效的排查顺序
命令不生效,按这个顺序查,基本能定位到:
- 清单里声明了吗:
commands数组里有没有这个文件。 - 文件在约定目录吗:是不是放在
commands/下。 - 文件名和声明一致吗:大小写、扩展名都要对。
- 插件被加载了吗:看启动日志有没有这个插件的加载记录。
- 重启了吗:Claude Code 通常在启动时扫描插件,改完不重启不生效。
这五步走下来,九成问题能解决。剩下那一成,多半是插件目录本身没在扫描范围内——检查你的插件目录配置,确认它指向了你放插件的那个路径。
5.3 技能不被调用的几个隐蔽原因
技能比命令更难排查,因为它是 Claude 自主判断调用的,不生效时你甚至不知道是"没加载"还是"加载了但没触发"。
先确认加载:技能目录结构对不对、SKILL.md在不在、清单里声明了没。这些都对了,再看触发。触发不灵通常是description写得太模糊。比如你写"处理数据",Claude 根本不知道什么时候该用;写成"当用户提供 CSV 文件并希望清洗数据时使用",触发率立刻上来。
我的经验是:技能描述要写"什么时候用",而不是"这是什么"。前者是触发条件,后者是功能说明,Claude 靠前者做判断。
5.4 跨平台使用插件的注意事项
热词里有windows claude code 安装、claude code linux下载,说明跨平台用户不少。插件在跨平台时有几个点要注意。
路径分隔符是头号问题。清单和钩子配置里统一用正斜杠/,Claude Code 在 Windows 上也能正确解析。别用反斜杠,反斜杠在 JSON 里还得转义,徒增麻烦。
脚本兼容性是二号问题。钩子脚本如果用了 bash 特性,在 Windows 上可能跑不了。要么用跨平台的脚本语言(比如 Node.js),要么在配置里针对不同平台写不同命令。
换行符是三号问题。Git 在 Windows 上默认可能把 LF 转成 CRLF,某些脚本对换行符敏感。建议在仓库里加.gitattributes,强制脚本文件用 LF。
注意:跨平台插件测试时,别只在你自己机器上测。至少在一个不同系统的环境里跑一遍,很多问题只有换平台才暴露。
6. 我踩过的坑和几条实在建议
写插件这段时间,踩的坑不算少,挑几个最有代表性的说说。
第一个坑是过度设计。一开始我想做一个"全能插件",把命令、技能、钩子、MCP 全塞进去,结果清单越来越复杂,加载失败的概率也越来越高。后来我改成"一个插件只做一件事",每个插件小而专,加载稳定,排查也容易。插件这东西,宁可多几个小的,别搞一个大的。
第二个坑是忽略版本。清单里的version我一开始随便填,后来团队里两个人装了同名不同版本的插件,行为不一致,排查了半天才发现是版本问题。现在我的习惯是:每次改动插件内容,必须升版本号,哪怕只是改了个错别字。
第三个坑是不写文档。插件写完了,过两周自己都忘了某个命令是干嘛的。后来我在每个插件的plugin.json里把description写详细,在技能文件里把用法写清楚。这不是给别人看的,是给未来的自己看的。
最后分享一个实用技巧:建一个"插件沙盒"目录,专门用来试新插件。新插件先在沙盒里跑通,确认没问题了再挪到正式目录。这样即使插件有问题,也不会影响你日常用的那套配置。这个习惯帮我省了无数次"改坏配置导致 Claude Code 起不来"的麻烦。
插件生态还在演进,claude-plugins-official的规范也可能调整。但底层那套"声明式加载 + 严格校验"的逻辑是稳定的,把这套逻辑吃透,规范怎么变你都能跟上。