当你在 Claude Code 里同时挂着几十甚至上百个 Skills 时,真正难的不是"造一个技能",而是回答一个问题:面对同一个请求,Claude 到底会执行哪个技能? 本教程系统拆解 Skills 的多层仲裁机制——从同名覆盖链、Plugin 命名空间隔离、嵌套 monorepo 限定名,到 description 触发决策、调用权限控制、skillOverrides 仲裁层与 compaction 后的生命周期预算。读完你将能精确预测、诊断并解决任意复杂的技能冲突。
适用人群:已能独立编写 Skill 的开发者;维护大型 monorepo 或团队共享技能库的工程师;装了大量 Plugin 和社区技能、想理清"到底谁说了算"的重度用户。
/skills 菜单需要此版本)SKILL.md,理解 description / allowed-tools 等基础字段settings.json 的权限规则语法很多人把"技能冲突"当成一个单一问题,实际上它发生在两个正交的维度上,必须分开诊断:
flowchart LR
A[一个用户请求进入] --> B{维度一:覆盖解析}
A --> C{维度二:触发选择}
B --> B1[同名技能之间<br/>谁替代谁]
C --> C1[同名之外<br/>Claude 调哪个]
B1 --> D[确定性规则<br/>可精确预测]
C1 --> E[基于 description<br/>的概率决策]
style D fill:#d4edda
style E fill:#fff3cd
description 做的概率性决策。它受上下文、措辞、甚至 compaction 状态影响。混淆这两个维度是排障失败的头号原因。下面逐一拆解。
Skills 按存储位置分为多个级别。官方给出的覆盖规则是单向链条:
| 级别 | 路径 | 覆盖能力 |
|---|---|---|
| Enterprise(托管设置) | 见 managed settings | 覆盖所有人,最高 |
| Personal(个人) | ~/.claude/skills/<name>/SKILL.md |
覆盖 Project 与 Bundled |
| Project(项目) | .claude/skills/<name>/SKILL.md |
覆盖 Bundled |
| Plugin | <plugin>/skills/<name>/SKILL.md |
独立命名空间,不参与覆盖链 |
| Bundled(内置) | 随 Claude Code 分发 | 被任何自定义同名技能覆盖 |
核心法则一句话:
enterprise overrides personal, and personal overrides project. A skill at any of these levels also overrides a bundled skill with the same name.
这条规则的实战价值极高。一个典型场景:你觉得内置的 /code-review 检查项不符合团队规范,只需在项目里建一个 .claude/skills/code-review/SKILL.md,它就会完整替换内置版——无需改任何全局配置,无需禁用内置项。
Plugin skills 不走覆盖链,而是挂在一个命名空间下:
my-plugin/skills/review/SKILL.md → /my-plugin:review
这意味着即便你的项目里也有一个 review 技能,两者也能共存——Plugin 版用 /my-plugin:review 调用,项目版用 /review 调用。这是 Plugin 作为可分发单元的关键设计:插件作者永远不用担心和用户的本地技能撞名。
历史遗留的 .claude/commands/deploy.md 与新式 .claude/skills/deploy/SKILL.md 功能等价(自定义命令已合并进 Skills)。但若两者同名,规则明确:skill 优先。迁移到 Skills 后建议删除同名 command 文件,消除歧义。
# 1. 在项目内创建一个会覆盖内置 code-review 的技能
mkdir -p .claude/skills/code-review
cat > .claude/skills/code-review/SKILL.md <<'EOF'
---
description: 团队定制代码审查。使用场景:用户请求审查代码、review diff、检查提交前质量。
---
## 审查清单(团队规范 v3)
- 必须检查是否有硬编码密钥
- 必须检查是否有未处理的 Promise
- 必须验证错误码符合 RFC 标准约定
- 不关注代码风格,风格由 lint 处理
EOF
预期结果:在同一会话内输入 /code-review,触发的是上面的团队版而非内置版。注意——由于 Skills 支持实时变更检测(live change detection),新建的技能无需重启即可生效,但前提是该 .claude/skills/ 目录在会话启动时已存在;如果是全新创建的顶层 skills 目录,仍需重启 Claude Code 以挂载目录监听。
这是覆盖链里最容易被忽略、却最容易引发"为什么跑错了技能"的环节。
在 monorepo 中,子包可以有自己的技能。当一个嵌套技能与其它层技能同名时,二者并存而非互相覆盖:
项目根: .claude/skills/deploy/SKILL.md → /deploy
子包: apps/web/.claude/skills/deploy/SKILL.md → /apps/web:deploy
仲裁规则:
/deploy → 永远执行项目根的版本。/apps/web:deploy(目录限定名)。常见问题与解决:你在 apps/web/ 下改代码,却感觉 deploy 流程用的是根目录的全局部署脚本。原因正是 Claude 根据文件路径仲裁——若子包技能的 description 没有写清"仅适用于 apps/web 前端",模型就缺少仲裁依据。修复方式:给嵌套技能的描述加上明确的作用域声明,例如:
description: 部署 apps/web 前端到 CDN。使用场景:在 apps/web/ 目录下工作、用户要部署前端、前端构建产物发布。
当不存在同名问题时,Claude 在一堆候选技能中选谁,完全取决于 description(含 when_to_use)的语义匹配。这里没有确定性优先级,只有一个概率模型在匹配你的措辞。
官方文档的关键数字——description 与 when_to_use 合并后,在技能列表里被截断到 1536 个字符。超过的部分模型根本看不到。这意味着:
maxSkillDescriptionChars 配置项调整。更隐蔽的问题:即便单个描述没超 1536,当你安装了大量技能时,所有描述共享一个总字符预算。预算不够时,低优先级技能的描述会被省略,模型也看不到它们——这就是排障章节里"Skill descriptions are cut short"的根因。
三个调节旋钮:
| 旋钮 | 类型 | 作用 |
|---|---|---|
skillListingBudgetFraction |
settings | 设为 0.02 表示把总预算提升到上下文的 2% |
SLASH_COMMAND_TOOL_CHAR_BUDGET |
环境变量 | 直接设定一个固定字符总数 |
skillOverrides 里 "name-only" |
settings | 让低优先级技能只列名字、不占描述预算 |
差的描述(语义模糊、易撞):
description: 帮助处理代码。
好的描述(高区分度、含触发措辞):
description: 生成符合 Conventional Commits 规范的提交信息。使用场景:用户要求 commit、写提交信息、整理变更说明、询问改了什么需要提交。
原则:每个技能的描述都应回答"我和隔壁那个功能相近的技能,区别在哪"。区分度越高,仲裁越准。
当 description 仲裁不够可靠时,用确定性字段兜底。这三个字段决定了"谁有权发起这次调用":
| Frontmatter | 用户能否调 | Claude 能否自动调 | 描述是否进上下文 | 典型用途 |
|---|---|---|---|---|
| 默认 | ✅ | ✅ | ✅ 始终在 | 普通技能 |
disable-model-invocation: true |
✅ | ❌ | ❌ 不进上下文 | 有副作用的动作(deploy/commit) |
user-invocable: false |
❌(菜单隐藏) | ✅ | ✅ 始终在 | 背景知识型技能 |
两个高频踩坑点:
user-invocable: false 不能阻止程序调用。它只控制 / 菜单的可见性。要真正禁止 Claude 通过 Skill 工具调用某技能,必须用 disable-model-invocation: true。disable-model-invocation: true 有连带效应:它不仅禁止自动触发,还把该技能从子代理的预加载列表中移除——这对 context: fork 类技能影响很大。实战示例——一个只允许手动触发、且自动放开 git 权限的安全 commit 技能:
---
name: commit
description: 按团队规范暂存并提交当前变更。使用场景:用户明确要求提交、commit、生成提交。
disable-model-invocation: true
allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status *)
---
设置 disable-model-invocation: true 后,即使你说"代码看起来可以了",Claude 也不会擅自提交——时序控制权回到你手里。
paths 限定——按文件路径仲裁paths 字段是触发仲裁中最被低估的护栏。它用 glob 模式限定技能只在操作匹配文件时才被自动加载:
---
description: 应用本仓库的 Go 错误处理规范。
paths: "**/*.go"
---
价值在于:当你的个人技能库里有几十个技能,但只有进 Go 项目、操作 .go 文件时才需要"Go 错误处理规范"。paths 让它在不相关项目里彻底沉默,不占触发候选、不占描述预算。这对跨语言、跨技术栈的重度用户是减噪利器。
skillOverrides 与全局开关有时你想仲裁的技能不在你能编辑的范围内——比如团队共享仓库里的技能、MCP 服务器提供的技能。这时用 settings.json 的 skillOverrides 层,而不是改 SKILL.md:
{
"skillOverrides": {
"legacy-api-helper": "off",
"noisy-but-useful": "name-only"
}
}
"off":完全关闭,从 Claude 上下文中移除。"name-only":只保留名字、省略描述,为其它技能腾出预算。"on"。skillOverrides 影响,需通过 /plugin 管理。更便捷的操作方式是用 /skills 菜单:高亮某技能,按 Space 在 on / name-only / off 间循环切换,按 Enter 自动写入 .claude/settings.local.json。
如果需要彻底禁用所有内置技能(/code-review、/debug、/loop 等),用 disableBundledSkills 设置;或用权限规则在 /permissions 中 deny 整个 Skill 工具,也可精细到 Skill(commit) 只允许特定技能。
技能内容一旦被调用,就会作为一条消息留在会话里直到结束——除非触发 auto-compaction。压缩时的仲裁规则决定了"哪些技能在长会话里仍然有效":
实战含义:长会话里"技能突然失效了"往往不是 bug,而是它在 compaction 后被挤出了预算。解决方法:在关键节点重新调用一次该技能以恢复完整内容;或精简技能正文,让它整体小于 5000 tokens 从而安全存活。
flowchart TD
Start[用户请求] --> Q1{存在同名技能?}
Q1 -->|是| Q2{是 Plugin 技能?}
Q2 -->|是| NS[命名空间隔离<br/>plugin:skill 共存]
Q2 -->|否| Q3{是嵌套 monorepo 同名?}
Q3 -->|是| NEST[按目录限定名仲裁<br/>依据当前操作文件]
Q3 -->|否| CHAIN[覆盖链: 企业>个人>项目>内置]
Q1 -->|否| Q4{有 disable-model-invocation?}
Q4 -->|是| MAN[仅用户手动 /name 触发]
Q4 -->|否| Q5{描述在预算内且未超 1536 字符?}
Q5 -->|否| MISS[模型看不到<br/>不会触发]
Q5 -->|是| Q6{paths 限定匹配当前文件?}
Q6 -->|否| SILENT[沉默不加载]
Q6 -->|是| MATCH[Claude 用 description 语义匹配]
MATCH --> EXEC[执行该技能]
NS --> EXEC
NEST --> EXEC
CHAIN --> EXEC
MAN --> EXEC
EXEC --> Q7{长会话触发 compaction?}
Q7 -->|是| BUD[保留每个技能前 5000 token<br/>共享 25000 总预算<br/>从最近调用填充]
Q7 -->|否| END[正常保留]
style MISS fill:#f8d7da
style SILENT fill:#f8d7da
style EXEC fill:#d4edda
style BUD fill:#fff3cd
场景:一位开发者从社区装了大量 Skills,现在抱怨"该触发的没触发、不该触发的乱触发、长会话里技能经常失效"。按下述流程逐层仲裁诊断。
Step 1——排除覆盖层噪音。运行 /skills,观察列表。若发现某些技能从未出现,检查是否被同名的高级别技能(个人级覆盖项目级)静默替换,或被 skillOverrides 设为 off。
Step 2——检查描述预算。若技能数量极多,低优先级技能描述大概率被预算截断。临时设环境变量诊断:
# 把总预算提到一个较大值,看触发是否改善
export SLASH_COMMAND_TOOL_CHAR_BUDGET=60000
若改善,说明根因是预算竞争。长期方案:把不常用技能在 skillOverrides 里设 "name-only",为核心技能腾出空间。
Step 3——用基准对比量化触发。官方给出的判定法是带对照的基准测试:收集若干真实提示词,每个在全新会话里分别跑"技能可用"和"技能禁用"两次,对比结果。必须用新会话,因为编写技能时残留的上下文会掩盖指令缺陷。
Step 4——交给 skill-creator 做 description 调优。skill-creator 的 description tuning 能力会自动生成 should-trigger / should-not-trigger 提示词,测量命中率,并给出描述修改建议。这是把"祈祷式触发"升级为"工程化触发"的关键工具。
Step 5——对有副作用的动作加确定性护栏。所有 deploy、commit、发消息类技能统一加 disable-model-invocation: true,杜绝 Claude 基于语义误判而擅自执行。
| 症状 | 根因 | 解决 |
|---|---|---|
| 技能从不触发 | 描述被预算截断 / 被高级别同名技能覆盖 / paths 不匹配 / 被 skillOverrides 关闭 |
提升预算、精简描述前置关键信息、检查 paths、检查 settings |
| 技能触发太频繁 | 描述过宽、与其它技能语义重叠 | 收紧描述、明确边界、必要时 disable-model-invocation: true |
| 技能描述被截断 | 总预算不足 / 单条超 1536 字符 | skillListingBudgetFraction / SLASH_COMMAND_TOOL_CHAR_BUDGET / maxSkillDescriptionChars / name-only |
一个反直觉的提醒:"看到技能触发"只证明 Claude 找到了它,不证明它做了你想要的事。触发命中率和输出符合度是两个独立指标,必须分开测量——这正是基准对比法的价值所在。
迁移到生产环境前,逐条核对你的技能库:
disable-model-invocation: true?name-only 策略?/plugin 而非 skillOverrides 管理?paths 限定,避免在不相关项目里噪声触发?code.claude.com/docs/en/skills——所有仲裁规则的权威来源code.claude.com/docs/en/plugins-reference——Plugin 命名空间与 schema 完整规范掌握这套仲裁模型后,无论你的技能库扩张到多大规模,都能精确回答那个终极问题:面对这个请求,到底谁说了算。
适用类型:中高级开发者训练营、企业内部 Claude Code 推广营、DevOps/平台工程方向的内训。
前置模块:建议放在"Skill 基础开发"与"Plugin 打包分发"之后,作为进阶治理模块。
预计教学时长:理论讲解 60 分钟 + 动手实验 90 分钟。实验建议给学员一个预置了同名冲突、嵌套冲突和描述撞车的"问题 monorepo",让他们按本文流程图逐层诊断修复。
考核点:能否预测给定配置下的最终生效技能;能否用基准对比法量化触发命中率;能否正确选择 frontmatter 字段而非修改 SKILL.md 来仲裁他人技能。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
