SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

如何跨仓库共享 Agent Skills 并避免版本漂移

8 个仓库复制同一 skill 会在第 60 天开始分叉。本文介绍共享 skills 仓库、项目固定标签、冒烟测试和版本评审流程。

如何在多个仓库之间共享 agent skill

要在多个仓库之间共享 agent skill,请停止复制文件,改为依赖该文件。维护一个 skills 仓库,为其创建标签,并让每个项目固定使用某个标签。然后为每个 skill 添加冒烟测试,并按照审核依赖项升级的方式审核每次版本更新。

这包括四个部分:共享的事实来源、每个仓库固定的版本、每个 skill 的冒烟测试,以及审核流程。下文将说明每个部分存在的原因、2026 年发布的工具如何处理这些问题,以及如何在自托管 git 远程仓库上构建完整方案,不涉及外部 git 主机或软件包注册表。冒烟测试是唯一的例外。测试会运行 Claude Code,而 Claude Code 会将每个请求发送到模型 API,因此这一步仍会离开您的服务器。

agent skill 是一个目录,其中包含 SKILL.md 文件,以及它所需的脚本和参考文件。如果该单元是新概念,请先阅读什么是 agent skill 以及 SKILL.md 的工作方式。本页介绍的是围绕该单元建立的供应链。

技能存放位置,以及为什么难以共享

Claude Code 会从 4 个位置加载技能,技能文档列出了每个路径。

  • .claude/skills/<skill-name>/SKILL.md 位于受管设置目录中,是企业级位置。在组织部署该目录的计算机上,它会为每个用户加载。
  • ~/.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 键。文件中没有任何内容记录哪个副本更新。这样设计是合理的,因为技能是文档,而不是软件包。但这意味着版本管理必须由文件外层负责,而这一层需要您自行管理。

问题一:8 份副本在不知不觉中逐渐分叉

复制粘贴在第一天有效。到第60天就会失效。有人修正了 payments 仓库中的错误说明,却没有修改其他7份副本。另一个人在 orders 中添加了分页规则。现在,相同的技能名称会产生两种不同的审查结果,具体取决于代理从哪个目录启动,而两个开发者都不知道原因。

这种故障不会显式报错,因为不存在错误状态。技能的内容是散文。过时的说明会导致代理自信地给出错误答案,而这类错误代价最高。代理不会将你的副本与其他人的副本进行比较,因此唯一的信号就是有人发现两个仓库的内容不一致。

问题二:没有固定版本

即使团队将技能集中存放,通常也只是通过复制步骤来共享:设置脚本、入职文档中的一行 curl,或用于同步目录的 shell 别名。这些方式安装的都是当前分支最新提交中的内容。

这意味着,同一应用处于同一提交时,两个开发人员运行的指令仍可能不同,因为他们在不同日期执行了同步。发生代理运行异常后,还有一个关键问题无法回答:产生该结果的技能版本是什么?如果没有记录修订版本,这次运行就无法复现,错误报告也无法用于定位问题。

问题三:没人知道技能是否仍然有效

技能没有编译器。它是提供给模型的指令,因此即使文件逐字节保持不变,也可能停止工作。模型升级会改变其严格遵循长指令的程度。技能调用的命令行工具可能会重命名某个标志。参考文件中的 URL 可能开始返回 404,代理则会根据错误页面继续工作。

这些情况都不会明确报告失败。代理仍然会回答。只是答案比上个月更差,而这种变化很难通过一次次 pull request 发现。

2026 年工具版本解决的问题

目前已经出现了几种解决方案,但它们对于版本信息应存放在哪里仍有不同看法。

锁文件。 Vercel Labs 的 skills 命令行工具(vercel-labs/skills,采用 MIT 许可证,截至 2026 年 10 月 9 日为 v1.7.2)可以从 git 仓库将技能安装到代理所需的目录,并且支持 70 多种代理的目录结构。npx skills add <repo> 用于安装,npx skills update 用于升级,npx skills list 用于显示已安装内容。该工具维护两个锁文件。全局锁文件位于 ~/.agents/.skill-lock.json,记录单个用户安装的内容。从 v1.4.1(2026 年 2 月 20 日)开始,安装到项目作用域还会在项目根目录写入 skills-lock.json;该工具的源代码表明,此文件应提交到版本控制系统。npx skills experimental_install 根据该文件恢复项目所需的技能。该项目的一个未解决请求(issue 283)仍要求提供 skills install 命令,用于重新安装全局锁文件中的全部技能,从而让第二台机器也拥有相同的个人技能集合。应将这两点都视为当前状态说明。项目级锁文件已经存在。根据它恢复技能的命令仍被标记为实验性功能。

规范和测试。 SkillSpec 采用另一种思路。它将 SKILL.md 视为需要检查的契约,而不是可以直接信任的说明文字;其目标是让技能“可遵循、可测试、可证明”。skillspec doctor <path> 报告代理可能中断执行的位置。skillspec boundary map <path> 显示技能包含的内容,skillspec boundary assess <path> 则按风险排序报告技能能够访问的内容。它是一个采用 MIT 或 Apache 2.0 双重许可证的 Rust crate。0.2.2 版本于 2026 年 7 月 29 日发布,截至 2026 年 10 月 10 日仍是 crates.io 上的最新版本。请安装指定版本,而不是最新版本:

cargo install skillspec --version 0.2.2 --locked
skillspec --version

--locked 使用该 crate 发布时所采用的依赖版本进行构建,因此构建过程不会在后台发生版本漂移。skillspec --version 应输出 0.2.2。如果显示其他数字,说明 PATH 中更早出现的路径包含的旧二进制文件优先被执行。

供应商实践。 Google 在一篇介绍其构建、测试和扩展代理技能方式的文章中,说明了它如何构建 google/skills 中的技能。去除规模因素后,这套机制就是常规的持续集成(CI)。每项技能在合并前都必须通过针对 frontmatter 元数据、行数、目录结构和命名的 linter 检查。链接检查器会在任何 URL 返回 404 时使构建失败,从而捕获代理自行生成的、看似合理的链接。作者必须随技能一并提供评估提示套件和评分规则。之后,定期运行的评估任务每周都会针对整个技能库执行,以发现回归问题;每项技能还必须指定负责人,在质量下降时负责修复。

三个答案背后的共同模式

您不必从中选择一个。它们背后是同一种结构,而原生 git 已经提供了完整支持。

  1. 单一事实来源。每个 skill 只有一个存放位置,每个仓库都引用该位置,而不是保存副本。
  2. 每个仓库固定一个版本。每个项目记录所使用的确切修订版本,因此升级就是在该项目中提交一次变更,并带有作者和日期。
  3. 每个 skill 一个冒烟测试。通过一个可运行的检查,验证该 skill 仍能产生其承诺的结果。
  4. 评审流程。共享 skill 的变更必须经过评审,每个使用方在采用变更前都能看到差异。

这就是依赖项的结构。skill 成为共享工件的速度超过了配套工具的发展速度,因此,优先使用您已经信任的工具是最安全的做法。

面向小型团队的自托管 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 行是审查路径。它会显示所有其他使用该模块的仓库将看到的相同变更,并且可以直接放入拉取请求。

改用插件市场进行固定

如果不想要求每位开发者学习 submodule,可以使用 Claude Code 插件系统完成分发,并将其指向自托管远程仓库。在 skills 仓库的 .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 是实际生效的固定版本。因此,精确提交固定应放在目录条目中。类型名称也不同。在目录中,url 表示要克隆的 git 仓库。在市场源中,url 表示直接链接到 marketplace.json 文件,而 git 仓库应使用 git。

每个使用插件的仓库都应在已提交的 .claude/settings.json 中声明该市场:

{
  "extraKnownMarketplaces": {
    "acme-agents": {
      "source": {
        "source": "git",
        "url": "https://git.example.com/team/agent-skills.git",
        "ref": "v1.4.0"
      }
    }
  },
  "enabledPlugins": {
    "team-skills@acme-agents": true
  }
}

队友接受项目目录的 workspace trust 对话框后,Claude Code 会为其注册该市场,无需通过 wiki 页面指导他们完成注册。目录条目指向 git URL,而不是相对路径,因此插件不会仅根据仓库设置自动安装。每位队友运行一次 claude plugin install team-skills@acme-agents --scope project;在此之前,Claude Code 会报告项目设置中已启用该插件,但插件尚未安装。之后,这些 skills 通过 /team-skills:api-review 响应,因为插件 skills 使用插件名称作为命名空间,不会与同名项目 skill 冲突。推送新标签后,使用方先通过 /plugin marketplace update acme-agents 刷新,然后在安装摘要要求时运行 /reload-plugins。

为单个技能编写冒烟测试

冒烟测试是针对包含已知故障的 fixture 运行脚本化代理,并执行一项断言。Claude Code 可通过 -p 以非交互方式运行,用户调用的技能在此模式下同样有效:将 /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/null

fixtures/orders-api.md 是一个包含一个故意故障的简短文件。断言要求技能指出该故障。当其过滤器产生 null 时,jq -e 会以非零状态退出,因此如果技能不再捕获预置故障,脚本就会失败。claude 本身会在运行失败时以非零状态退出,而 set -euo pipefail 会将任一失败转换为测试失败。

模型在不同运行中会改写回答,因此不要对完整句子执行断言。应断言技能应输出的标识符,或所要求架构中的某个字段,并保持 fixture 简短,以降低运行成本。

在 CI 中添加 --bare。否则,claude -p 会加载与交互式会话相同的上下文,包括运行机器上的钩子、插件和 CLAUDE.md,因此团队成员的个人配置可能改变结果。裸模式会跳过所有自动发现,因此也会跳过正在测试的技能;请显式加载该技能。裸模式也不会读取订阅登录信息,因此应先在环境中设置 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

使用 --plugin-dir 加载的插件会从 .claude-plugin/plugin.json 获取名称;如果没有该清单,插件名称会使用其目录名。请在技能仓库的目录旁,与目录一起提交 plugin.json 和 "name": "team-skills";否则,上面的命令必须改为调用 /agent-skills:api-review。

使用 --output-format stream-json 时,system/init 事件会报告已加载的插件,并包含一个 plugin_errors 数组,列出未加载的插件。如果启动事件先发生,它会打开事件流。若 plugin_errors 非空,则让 CI 作业失败。这样可以捕获指向已不存在版本的固定引用;否则,代理可能只是悄悄忽略你规定的团队规则。

共享技能就是可执行指令

当文件来自其他团队时,以下两个功能会让这句话成为字面事实,而且两者都很重要。

首先,SKILL.md可以在模型读取任何内容之前执行 shell 命令。正文中的以下行就是预处理指令:

- Current branch: !`git rev-parse --abbrev-ref HEAD`

该命令会在加载技能的机器上执行,其输出会替换模型收到的文本中的占位符。以三个反引号开头、后跟 ! 的围栏代码块也会以相同方式执行多个命令。运行时不会为这些操作逐一提示。Claude Code 会根据权限规则检查每条命令。在自动模式之外,权限规则不允许的命令会中止技能,除非技能通过下面介绍的第二个功能预先批准了该命令。阅读共享技能,就意味着要阅读其中的命令替换内容。

其次,frontmatter 可以预先批准工具。allowed-tools 会在调用该技能的本次操作中授予所列工具的权限,且不会显示权限提示。工作区信任不会限制这项授权:即使在未受信任文件夹中以 -p 运行,Claude Code 仍会应用项目技能的 allowed-tools。Claude Code 文档明确说明了后果:技能可以自行授予广泛的工具访问权限。因此,在仓库中运行 Claude Code 之前,应审查其中技能的 allowed-tools。组织可以在托管设置中配置 allowManagedPermissionRulesOnly,禁止项目技能和个人技能使用该字段;这需要 Claude Code v2.1.282 或更高版本。

因此,应完全按照升级依赖项的方式处理技能更新。在机制允许的情况下,使用精确的 commit 锁定版本,因为 tag 可能被移动,而 branch 本身就会持续移动。在受限机器上,设置中的 "disableSkillShellExecution": true 会将每个命令替换为字面文本 [shell command execution disabled by policy],而不是执行命令;如果通过托管设置应用,用户无法覆盖该设置。内置技能和托管技能不受该设置影响。

对于技能读取的内容,也应采取同样的谨慎态度。执行 env 或打开配置文件的技能,会将它找到的全部内容加载到模型上下文中。这正是 让机密信息远离所运行的代理 所介绍的风险。获取网页或执行查询的技能,会将同样的暴露面扩展到外部:检索到的文本会进入上下文,看起来与您编写的指令完全相同。在 将代理指向自己的 SearXNG 实例进行网页搜索 之前,应先了解这一边界。

版本升级时需要阅读的内容

  • 每个 SKILL.md 正文的差异,因为其中的文本就是代理将遵循的指令。
  • 每个命令替换,因为技能加载时会在您的计算机上执行这些命令。
  • 对 allowed-tools 的任何更改,因为该行无需提示即可授予工具访问权限。
  • 标签背后的测试运行结果。如果共享仓库在 CI 中运行自己的冒烟测试,则您固定使用的标签应附带一次通过的运行结果。

如果审阅者无法在十分钟内读完整个差异,说明该技能已经过于庞大。请将其拆分。同样的原则也适用于代理读取的仓库文档:将持久规则保存在 AGENTS.md 与 HUMAN.md 的拆分所描述的文件中,将架构方面的理由写入面向代理编写的 DESIGN.md,而让技能只包含范围明确的操作流程。

当模型或工具变更破坏技能

技能本身没有人编辑,但其底层的多个组件可能发生变化。模型升级会改变长指令的执行可靠性,因此依赖模型执行到第九步的技能可能不再执行到那里。命令行工具重命名了某个 flag,代理仍使用旧 flag,读取错误后自行推断。被引用的 URL 开始返回 404。代理框架改变了技能的选择方式,因此原本能够匹配成功的 description 不再胜出。当某个流程开始提前结束时,增加版本号无法修复问题;必须调整指令结构,强制执行最后几个步骤。这正是unlazy 技能及其 Depth Tree 方法所采用的方式。

因此,在这种安排中,冒烟测试承担了主要作用。除了在 push 时运行每个技能的测试,还应按计划运行。Google 每周都会针对整个技能库运行评估任务,原因就在于此。对于只有十个技能的团队,在一台小型 VPS 上设置每周 cron 任务就足够了。这是开发人员发现故障前获知问题的唯一方法。

可移植性同样有帮助。Agent Skills 规范将 frontmatter 限制为 6 个键,因此按照该规范编写的技能可以加载到编写目标工具之外的其他工具中;而每增加一个特定于框架的键,都是在押注某个供应商。编写能够适应模型更换的技能本身是一项专门实践,详见让技能适用于任何模型。

FAQ

如何在多个仓库之间共享一个 agent skill?

将 skill 放在专用 git 仓库中,为其发布版本打 tag,然后让每个使用方项目引用 tag,而不是复制文件。有两种机制可用。git submodule 记录精确的 commit,从 .claude/skills/<name> 指向该 submodule 的符号链接会使它作为普通项目 skill 加载。插件 marketplace 通过 /plugin 完成同样的工作,并在使用方仓库的 .claude/settings.json 中声明固定版本。两种方式都会将版本记录在 git 历史中,因此可以确定某次 agent 运行使用了哪些指令。

可以将 agent skill 固定到特定版本吗?

不能在 SKILL.md 内完成,因为其中的 frontmatter 没有 version 键。固定版本必须由文件外层的机制提供。git submodule 的设计就是固定精确的 commit。在 Claude Code 插件 marketplace 中,插件源接受 ref 来指定分支或 tag,接受 sha 来指定精确的 commit;两者同时存在时,sha 优先。marketplace 源本身只接受 ref。优先使用 commit 固定,因为在审核后 tag 仍可能被移动。

skill 的 smoke test 应断言什么?

应断言稳定的结果。针对包含已知故障的 fixture,以非交互方式运行 skill,然后检查输出中是否出现特定标识符,例如 skill 应报告的规则 id。使用 --output-format json 和 --json-schema 请求结构化输出,可以使检查精确;jq -e 会在缺少该值时使脚本失败。不要断言完整句子,因为模型在不同运行之间可能改写答案。

从其他团队的仓库安装共享 skill 是否安全?

应将其视为代码依赖,因为它属于可执行指令。SKILL.md 可以在加载时通过 ! 命令替换形式运行 shell 命令,frontmatter 的 allowed-tools 字段可以在无需提示的情况下预先批准工具。在每次升级时查看 diff,固定到精确的 commit 而不是分支,并优先使用由本团队控制的源。在受管控的机器上,设置中的 "disableSkillShellExecution": true 会完全阻止命令替换运行。

共享 skill 能否在 Claude Code 以外的 agent 中运行?

这取决于使用了哪些 frontmatter。Agent Skills 规范定义了 6 个键:name、description、license、compatibility、metadata 和 allowed-tools。仅使用这些键的 skill 可以在实现该规范的工具之间加载,也可以不做修改地加载到 Claude Code 中。其他工具特有的键以及规范之外的正文功能,可能会在其他工具中被忽略或拒绝,因此计划广泛共享的 skill 不应包含这些内容。