如何跨仓库共享代理技能并避免版本漂移
将技能复制到8个仓库后容易悄然分叉。本文介绍共享技能仓库、Git标签固定版本、冒烟测试和升级审查流程,且无需依赖外部服务。
如何在多个仓库之间共享代理技能
要在多个仓库之间共享代理技能,请停止复制文件,改为依赖该文件。维护一个技能仓库,为其创建标签,并让每个项目固定使用某个标签。然后为每项技能添加冒烟测试,并像审查依赖项升级一样审查每次版本更新。
这包括四个部分:共享的事实来源、每个仓库固定的版本、每项技能的冒烟测试,以及审查流程。下文将说明每个部分存在的原因、2026 年发布的工具如何处理这些问题,以及如何在不依赖任何外部服务的情况下,基于自托管的 git 远程仓库构建完整方案。
代理技能是一个目录,其中包含一个 SKILL.md 文件,以及所需的脚本和参考文件。如果这是全新的单元,请先阅读什么是代理技能以及 SKILL.md 的工作方式。本页介绍的是围绕该单元的供应链。
技能存放位置,以及共享为何困难
Claude Code 会从三个位置加载技能,技能文档列出了每个路径。
~/.claude/skills/<skill-name>/SKILL.md是个人级路径。它会加载到您的所有项目中,其他用户无法使用。.claude/skills/<skill-name>/SKILL.md是项目级路径。检出该仓库的用户都可以加载它。<plugin>/skills/<skill-name>/SKILL.md随插件一起发布。在启用该插件的所有位置都会加载它。
对于团队而言,中间这一项最有用,因为它会提交到仓库,所有克隆该仓库的用户都能获得它。但问题也从这里开始。.claude/skills/ 中的技能属于单个仓库。您有 8 个仓库,因此该技能需要复制 8 份。
frontmatter 无法解决这个问题。Agent Skills 规范允许使用 6 个键,而负责强制执行该规范的分发路径在您使用其他键时会打印以下列表:
Unexpected key(s) in SKILL.md frontmatter: argument-hint. Allowed properties are: allowed-tools, compatibility, description, license, metadata, name请注意缺少的内容:其中没有 version 键。文件中没有任何信息记录哪个副本更新。这样设计是合理的,因为技能是文档,而不是软件包。但这意味着版本管理必须由文件外部的层负责,而这一层需要由您构建。
问题一:八份副本悄然分叉
复制粘贴在第一天有效,到第六十天就会失效。有人修正了 payments 仓库中的错误说明,却没有修改另外七份副本。另一个人在 orders 中添加了分页规则。现在,同一个技能名称会根据代理启动时所在的目录不同而产生两种不同的审查结果,而两个开发者都不知道原因。
这种故障不会显式报错,因为不存在错误状态。技能的内容是说明文字。过时的说明会生成看似可靠但实际错误的答案,而这类错误代价最高。代理不会将你的副本与其他人的副本进行比较,因此唯一的信号就是有人发现两个仓库中的内容不一致。
问题二:没有固定版本
即使团队将技能集中存放,通常的共享方式仍是复制:设置脚本、入职文档中的一行 curl,或用于同步目录的 shell 别名。所有这些方式安装的都是当前分支最新提交中的内容。
这意味着,即使两个开发者使用的是同一应用的同一提交,他们运行的指令也可能不同,因为两人在不同日期执行了同步。发生代理运行异常后,最关键的问题是:产生该结果的技能版本是什么?如果没有记录修订版本,这次运行就无法复现,错误报告也无法用于定位问题。
问题三:没人知道该 skill 是否仍然有效
skill 没有编译器。它是面向模型的指令,因此即使文件逐字节保持不变,也可能停止工作。模型升级后,对长篇指令的遵循程度可能发生变化。skill 调用的命令行工具可能重命名某个 flag。参考文件中的 URL 可能开始返回 404,导致 agent 根据错误页面工作。
这些情况都不会明确报错。agent 仍然会回答。只是答案比上个月更差,而这种变化很难通过一次次 pull request 及时发现。
2026 年发布的工具解决了哪些问题
现在已经有多个解决方案,但它们对于版本应存放在哪里仍存在分歧。
锁文件。 Vercel Labs 提供的 skills 命令行工具(vercel-labs/skills,采用 MIT 许可证,截至 2026 年 8 月 5 日版本为 v1.5.22)可以从 git 仓库安装技能,并将其放入代理所需的目录。它支持 70 多种代理的目录结构。npx skills add <repo> 用于安装,npx skills update 用于升级,npx skills list 用于显示当前已安装的技能。已安装内容的记录按用户保存,而不是按仓库保存。该项目有一个未解决的请求(issue 283),要求增加 skills install 命令,从锁文件重新安装所有已跟踪的技能,使第二台机器获得相同的技能集合。应将这个请求视为当前状态的说明。锁文件的思路已经确定,但按项目管理的部分仍在开发中。
规范和测试。 SkillSpec 采取了另一种方法。它将 SKILL.md 视为需要检查的契约,而不是可以直接信任的说明文字。其目标是让技能“可遵循、可测试、可证明”。skillspec doctor <path> 报告代理可能中断任务的位置。skillspec boundary map <path> 报告技能可以访问的内容,skillspec boundary assess <path> 则按风险对这些结果排序。它是一个 Rust crate,采用 MIT 或 Apache 2.0 双许可证。截至 2026 年 7 月 29 日,版本为 0.2.2。请安装指定版本,而不是最新版本:
cargo install skillspec --version 0.2.2 --locked
skillspec --version--locked 使用该 crate 发布时的依赖版本进行构建,因此构建过程不会在后台发生版本漂移。skillspec --version 应输出 0.2.2。如果显示其他数字,说明你的 PATH 中有更早的旧二进制文件优先被调用。
供应商实践。 Google 在一篇介绍其构建、测试和扩展代理技能方式的文章中,说明了它如何构建 google/skills 中的技能。去除规模因素后,其机制就是常规的持续集成(CI)。每个技能在合并前都必须通过前置元数据、行数、目录结构和命名检查。链接检查器会在任何 URL 返回 404 时使构建失败,从而发现代理编造出的看似合理的链接。作者必须随技能一并提供评估提示词套件和评分标准。随后,定期评估任务每周针对整个技能库运行,以发现回归问题;每个技能还都有一名指定负责人,质量下降时应由其负责修复。
三个答案背后的模式
您不必从这三个方案中选择一个。它们背后是同一种结构,而原生 git 可以完整实现这种结构。
- 单一事实来源。技能只有一个存放位置,每个仓库都引用该位置,而不是保存副本。
- 每个仓库固定一个版本。每个项目记录所使用的确切修订版本,因此升级就是在该项目中提交一次变更,并带有作者和日期。
- 每项技能都有一个冒烟测试。通过一个可运行的检查,确认技能仍能产生其承诺的结果。
- 变更审查路径。共享技能的变更必须经过审查,每个使用方在采用变更前都能看到差异。
这就是依赖项的结构。技能比围绕它们构建的工具发展得更快,因此,优先使用您已经信任的工具是最安全的做法。
小团队自托管 Git 远程仓库的布局
一个仓库用于存放技能内容。仓库中不存放其他内容,因此其历史记录就是一份指令变更日志。
agent-skills/
skills/
api-review/
SKILL.md
release-notes/
SKILL.md
tests/
api-review.sh
release-notes.sh
CHANGELOG.md发布版本使用标签。请使用附注标签,因为它包含消息和日期。标签消息应说明使用者为什么需要升级:
git tag -a v1.4.0 -m "api-review: require pagination on list endpoints"
git push origin v1.4.0无论远程仓库使用的是 Gitea、Forgejo、GitLab,还是自有 VPS 上通过 SSH 访问的裸仓库,后文内容都不需要修改。这里涉及的只有 git 和一个符号链接。
使用 git 子模块固定版本
子模块会在您的仓库中记录另一个仓库的一个确切提交。该记录就是固定版本。在每个使用该模块的项目中:
git submodule add https://git.example.com/team/agent-skills.git vendor/agent-skills
git -C vendor/agent-skills fetch --tags
git -C vendor/agent-skills checkout v1.4.0
mkdir -p .claude/skills
ln -s ../../vendor/agent-skills/skills/api-review .claude/skills/api-review
git add .gitmodules vendor/agent-skills .claude/skills/api-review
git commit -m "Pin shared agent skills to v1.4.0"符号链接是实现这一点的关键。项目级技能条目可以是指向磁盘其他位置目录的符号链接,Claude Code 会跟随该链接,并从目标位置读取 SKILL.md。因此,该技能会作为普通项目技能加载,而实际文件存储在子模块中,并固定到您选择的提交。
检查固定版本:
git submodule status正常的输出行以一个空格开头,依次包含提交、路径和最近的标签:
4d1a7c2f0b93e5a1c8d6f2b40e7a95c3d1f8b602 vendor/agent-skills (v1.4.0)开头的 - 表示子模块尚未初始化,因此 .claude/skills/api-review 指向空位置,技能不会加载,且不会显示错误。使用 git submodule update --init 修复。开头的 + 表示当前检出的提交与记录的提交不同,因此该开发者使用的是其他人没有的指令。新克隆的仓库需要执行 git clone --recurse-submodules。该命令应写入 README,因为普通克隆会使 vendor/agent-skills 为空,且不会输出错误。
升级必须经过明确操作,这正是固定版本的意义:
git -C vendor/agent-skills fetch --tags
git -C vendor/agent-skills diff v1.4.0 v1.5.0 -- skills/
git -C vendor/agent-skills checkout v1.5.0
git add vendor/agent-skills
git commit -m "Bump shared agent skills to v1.5.0"diff 行是审查路径。它会显示其他使用该模块的仓库将看到的相同变更,并且适合放入拉取请求。
改用插件市场进行固定
如果您不希望要求每位开发者学习子模块,Claude Code 插件系统可以代您完成分发,并支持使用自托管远程仓库。将目录文件放在技能仓库的 .claude-plugin/marketplace.json 中:
{
"name": "acme-agents",
"owner": { "name": "Platform team", "email": "platform@example.com" },
"plugins": [
{
"name": "team-skills",
"description": "Shared review and release skills",
"version": "1.4.0",
"source": {
"source": "url",
"url": "https://git.example.com/team/agent-skills.git",
"ref": "v1.4.0",
"sha": "4d1a7c2f0b93e5a1c8d6f2b40e7a95c3d1f8b602"
}
}
]
}这里涉及两个不同的源,混淆两者是常见错误。市场源指获取目录本身的位置,接受用于指定分支或标签的 ref,不接受 sha。目录中的插件源同时接受这两个参数;如果两者都设置,则 sha 是实际生效的固定版本。因此,精确提交固定应写在目录条目中。
每个使用插件的仓库随后都在已提交的 .claude/settings.json 中声明该市场:
{
"extraKnownMarketplaces": {
"acme-agents": {
"source": {
"source": "url",
"url": "https://git.example.com/team/agent-skills.git",
"ref": "v1.4.0"
}
}
},
"enabledPlugins": {
"team-skills@acme-agents": true
}
}信任项目目录的团队成员会收到安装市场的提示,插件也会自动为其启用,无需通过 wiki 页面告知他们手动操作。此后,技能通过 /team-skills:api-review 调用,因为插件技能使用插件名称作为命名空间,不会与同名项目技能冲突。推送新标签后,使用者通过 /plugin marketplace update acme-agents 刷新,然后在安装摘要要求时运行 /reload-plugins。
为一个 skill 编写冒烟测试
冒烟测试是:针对包含已知故障的测试夹具运行一次脚本化 agent,并执行一项断言。Claude Code 使用 -p 以非交互方式运行,用户调用的 skill 也可在此模式下工作:将 /skill-name 放入提示字符串中,运行开始前会对其进行展开。
#!/usr/bin/env bash
set -euo pipefail
claude -p "/api-review Read fixtures/orders-api.md and list the rule ids it breaks." \
--allowedTools "Read" \
--output-format json \
--json-schema '{"type":"object","properties":{"rule_ids":{"type":"array","items":{"type":"string"}}},"required":["rule_ids"]}' \
| jq -e '.structured_output.rule_ids | index("pagination-required")' > /dev/nullfixtures/orders-api.md 是一个包含一个明确故障的短文件。断言要求 skill 指出该故障。当过滤结果为 null 时,jq -e 会以非零状态退出,因此,如果 skill 不再捕获预置故障,脚本就会失败。运行失败时,claude 自身会以非零状态退出,而 set -euo pipefail 会将任一失败转换为失败的测试。
模型在不同运行中可能改写回答,因此不要对完整句子执行断言。应断言 skill 必须输出的标识符,或所要求架构中的某个字段,并保持测试夹具小巧,以控制运行成本。
在 CI 中加入 --bare。否则,claude -p 会加载与交互式会话相同的上下文,包括运行机器上的钩子、插件和 CLAUDE.md,因此团队成员的个人配置可能改变结果。裸模式会跳过所有自动发现,这也意味着它会跳过正在测试的 skill,因此需要显式加载该 skill。裸模式也不会读取订阅登录信息,因此应先在环境中设置 ANTHROPIC_API_KEY:
claude --bare -p "/team-skills:api-review Read fixtures/orders-api.md and list the rule ids it breaks." \
--plugin-dir vendor/agent-skills \
--allowedTools "Read" \
--output-format json使用 --output-format stream-json 时,运行的第一个事件会报告已加载的插件,并为未加载的插件提供 plugin_errors 数组。若 plugin_errors 非空,应使 CI 作业失败。这样可以捕获指向已不存在修订版本的固定配置;否则,agent 可能只是悄悄忽略你的团队规则。
共享技能就是可执行指令
当文件来自其他团队时,有两个特性会让这句话成为字面事实,而且两者都很重要。
首先,SKILL.md可以在模型读取任何内容之前运行 shell 命令。正文中的以下行会执行预处理:
- Current branch: !`git rev-parse --abbrev-ref HEAD`命令会在加载该技能的计算机上运行,其输出会替换模型收到的文本中的占位符。以三个反引号开头、后跟 ! 的围栏代码块也会以相同方式运行多个命令。运行时不会有人批准这些操作。读取共享技能,就意味着读取其中的命令替换内容。
其次,frontmatter 可以预先批准工具。allowed-tools会在调用该技能的本次交互中授予所列工具的使用权限,且不会弹出权限提示。对于项目技能,用户接受该文件夹的工作区信任对话框后,这项授权即会生效。Claude Code 文档明确说明了后果:在信任仓库之前,应先审查项目技能,因为技能可以自行授予广泛的工具访问权限。
因此,应完全按照升级依赖项的方式处理技能升级。只要机制允许,就使用精确 commit 固定版本,因为 tag 可能被移动,而 branch 按定义会持续移动。在受限计算机上,设置中的 "disableSkillShellExecution": true会将每个命令替换替换为字面文本 [shell command execution disabled by policy],而不是运行命令;通过托管设置应用后,用户无法覆盖该设置。捆绑技能和托管技能不受该设置影响。
对技能读取的内容也应采取同样的谨慎态度。运行 env 或打开配置文件的技能,会将找到的全部内容加载到模型上下文中。这正是 避免在所运行的代理中泄露机密 所涵盖的故障。
版本升级时需要检查的内容
- 每个
SKILL.md正文的差异,因为这些文本就是代理将遵循的指令。 - 每个命令替换,因为技能加载时会在您的计算机上执行这些命令。
- 对
allowed-tools的任何更改,因为该行会在无需提示的情况下授予工具访问权限。 - 标签对应的测试运行结果。如果共享仓库在 CI 中运行自己的冒烟测试,则固定使用的标签应附带一次通过的运行结果。
如果审阅者无法在十分钟内读完全部差异,说明该技能已经过于庞大。请将其拆分。同样的原则也适用于代理读取的仓库文档:将持久性规则保存在 AGENTS.md 和 HUMAN.md 的拆分 所述的文件中,将架构决策依据写入 面向代理编写的 DESIGN.md,并让技能保持为范围明确的操作流程。
模型或工具变更导致技能失效
技能下层的多个部分可能发生变化,即使没有人编辑技能本身。模型升级会改变其可靠遵循长指令的能力,因此依赖模型执行到第九步的技能可能无法再执行到这一步。命令行工具重命名了某个 flag,代理仍使用旧 flag,读取错误后自行处理。引用的 URL 开始返回 404。代理运行框架改变了技能选择方式,因此过去能够匹配成功的 description 不再胜出。
这就是为什么在这种安排中,冒烟测试如此重要。除了在 push 时运行,还应按计划运行每个技能的测试。Google 每周针对整个技能库运行评估任务,原因就在于此。对于拥有十个技能的团队,在一台小型 VPS 上运行每周 cron 任务就足够了。这是让你在开发者发现故障之前获知问题的唯一方式。
可移植性同样有帮助。Agent Skills 规范将 frontmatter 限制为六个键,因此按照该规范编写的技能可以加载到目标工具之外的其他工具中;而你添加的每个特定于运行框架的键,都是对某个厂商的一次押注。编写能够在更换模型后继续运行的技能本身就是一门实践,详见让技能适用于任何模型。
FAQ
如何在多个仓库之间共享一个 agent skill?
将 skill 放在专用 git 仓库中,在其中标记发行版本,然后让每个使用项目引用某个标记,而不是复制文件。有两种可行机制。git submodule 记录精确提交,并从 .claude/skills/<name> 创建指向该 submodule 的符号链接,使其作为普通项目 skill 加载。插件市场通过 /plugin 实现相同功能,并在使用方仓库的 .claude/settings.json 中声明固定版本。两种方式都会将版本写入 git 历史,因此可以确定某次 agent 运行使用了哪些指令。
可以将 agent skill 固定到特定版本吗?
不能在 SKILL.md 内完成,因为该 frontmatter 没有 version 键。固定版本必须由文件外层的机制提供。git submodule 的设计就是固定到精确提交。在 Claude Code 插件市场中,插件源接受 ref 指定分支或标记,并接受 sha 指定精确提交;两者同时存在时,sha 优先。市场源本身只接受 ref。应优先固定到提交,因为在你完成审核后,标记仍可能被移动。
skill 的 smoke test 应断言什么?
应断言稳定内容。针对包含已知故障的 fixture,以非交互方式运行 skill,然后检查输出中是否出现特定标识符,例如 skill 应报告的规则 id。使用 --output-format json 和 --json-schema 请求结构化输出,可以使检查结果精确;jq -e 会在缺少该值时使脚本失败。不要断言完整句子,因为模型在不同运行中可能会改写答案。
从其他团队的仓库安装共享 skill 是否安全?
应将其视为代码依赖,因为它属于可执行指令。SKILL.md 可以在加载时通过 ! 命令替换形式运行 shell 命令,frontmatter 的 allowed-tools 字段还可以在无需提示的情况下预先批准工具。每次升级都应查看 diff,固定到精确提交而不是分支,并优先使用由本团队控制的源。在受管控的计算机上,设置中的 "disableSkillShellExecution": true 可以完全阻止命令替换运行。
共享 skill 能在 Claude Code 之外的 agent 中运行吗?
这取决于使用了哪些 frontmatter。Agent Skills 规范定义了 6 个键:name、description、license、compatibility、metadata 和 allowed-tools。仅使用这些键的 skill 可以在实现该规范的工具之间加载,也可以在 Claude Code 中直接加载。其他工具专用的键,以及超出该规范的正文功能,可能会在其他工具中被忽略或拒绝;因此,计划广泛共享的 skill 不应包含这些内容。