跳转到内容

Claude Code skills:SKILL.md 全字段、谁能调用它、以及为什么内容进了上下文就不走了

约 25 分钟 难度:进阶 动手章

什么时候该写 skill:你在反复把同一段指令、清单或多步流程粘进对话,或者 CLAUDE.md 里某一节已经从「事实」长成了「流程」

和 CLAUDE.md 内容不同,skill 的正文只在被用到时加载,所以长参考资料在你需要它之前几乎不花钱。

但有一个关键限制常被忽略:skill 内容一旦进入上下文,就留到会话结束。Claude Code 不会在后续轮次重读 skill 文件。所以该在整个任务期间生效的指导要写成「常驻指令」的语气,不是「一次性步骤」。

自定义命令已经合并进 skills。.claude/commands/deploy.md.claude/skills/deploy/SKILL.md 都产生 /deploy,行为一致。已有的 .claude/commands/ 文件继续可用,skills 多了三样东西:放辅助文件的目录、控制调用权的 frontmatter、以及被 Claude 自动加载的能力。

位置路径作用范围
企业见 managed 设置组织全体
个人~/.claude/skills/<name>/SKILL.md你的所有项目
项目.claude/skills/<name>/SKILL.md仅这个项目
插件<plugin>/skills/<name>/SKILL.md插件启用处

同名时的覆盖顺序:企业 > 个人 > 项目。任意一层的 skill 也会覆盖同名的内置 skill——项目 .claude/skills/ 里的 code-review 会替换内置的 /code-review

插件 skill 用 plugin-name:skill-name 命名空间,不会和其他层冲突。

skill 和同名 command 同时存在时,skill 优先。

项目 skill 从你启动 Claude Code 的目录以及每一级父目录直到仓库根加载。在子目录里启动仍然能拿到根上定义的 skill。

启动目录下面的嵌套 .claude/skills/ 不在启动时加载。它们在 Claude 第一次读取或编辑那个子目录里的文件时加载,之后整个会话都可用。在那之前它们不出现在自动补全里,也不能按名字调用。

这是 monorepo 里的一个实际影响:packages/frontend/.claude/skills/ 里的 skill,在 Claude 碰过那个目录下的文件之前是「不存在」的。

嵌套 skill 和别的 skill 同名时:

  • 嵌套的那个以目录限定名出现,如 apps/web:deploy
  • 它的描述会说明它适用于哪个目录
  • Claude 挑选匹配它正在处理的文件的那个变体

/deploy 运行项目根的那个。敲 /apps/web:deploy 显式运行嵌套变体。

所有字段都是可选的,只有 description 是推荐的——Claude 靠它判断什么时候用这个 skill。

布尔字段接受 yesnoonoff10(任意大小写),以及 truefalse。v2.1.218 之前只认后两个。

字段说明
name列表里显示的名字。默认取目录名。个人和项目 skill 里它只影响显示,命令名仍来自目录名
description做什么、何时用。省略时取 markdown 正文第一段
when_to_use补充触发场景,追加在 description 后面
argument-hint自动补全时显示的参数提示,如 [issue-number]
arguments命名位置参数,用于 $name 替换。名字按顺序映射到参数位置
disable-model-invocationtrue 时只有你能调用
user-invocablefalse 时从 / 菜单隐藏,只有 Claude 能调用
allowed-tools调用这个 skill 的那一轮里可以不询问就使用的工具
disallowed-toolsskill 活跃期间从 Claude 可用工具池里移除的工具
model这个 skill 活跃时用的模型。覆盖只在当前轮次生效,不写进设置
effort这个 skill 活跃时的 effort level
context设为 fork 在 fork 出的 subagent 上下文里运行
agentcontext: fork 时用哪种 subagent 类型
background仅在 context: fork 时有效。设 false 则在调用它的那一轮等结果。默认 true
hooks限定在这个 skill 生命周期内的 hook
pathsglob 模式,限制何时自动激活这个 skill
shell!command“ 块用哪个 shell,bash(默认)或 powershell

descriptionwhen_to_use 的合并文本在 skill 列表里被截断到 1536 字符,所以把最关键的用例写在最前面

默认你和 Claude 都能调用任意 skill。两个字段各限制一个方向:

frontmatter你能调用Claude 能调用何时加载进上下文
(默认)描述常驻上下文,调用时加载全文
disable-model-invocation: true描述不进上下文,你调用时加载全文
user-invocable: false描述常驻上下文,调用时加载全文

disable-model-invocation: true 用于有副作用或需要你控制时机的流程:/commit/deploy/send-slack-message。你不希望 Claude 因为「代码看起来准备好了」就自己去部署。

user-invocable: false 用于背景知识——一个 legacy-system-context skill 解释老系统怎么工作,Claude 该在相关时知道,但 /legacy-system-context 对用户来说不是一个有意义的动作。

disable-model-invocation: true 还有两个附带效果:阻止这个 skill 被预加载进 subagent;v2.1.196 起,也阻止它在定时任务以它为提示词触发时运行。

内容生命周期:为什么「加载了就不走」很重要

Section titled “内容生命周期:为什么「加载了就不走」很重要”

你或 Claude 调用一个 skill 时,渲染后的 SKILL.md 内容作为一条消息进入对话,然后留在那里直到会话结束

三个推论:

Claude Code 不在后续轮次重读 skill 文件。所以该在整个任务期间适用的指导写成常驻指令,不要写成一次性步骤。

重复调用不会重复堆叠。Claude 重新调用一个 skill 时,如果渲染内容和上下文里已有的副本完全相同,Claude Code 只加一条「已加载」的短提示,不加第二份内容。渲染内容不同时(参数变了,或动态上下文命令产出了新输出)才追加完整内容。v2.1.202 之前每次重新调用都追加一份完整副本。

权限授予的生命周期和内容不同allowed-tools 的授予在你发送下一条消息时就清除了,即使 skill 内容还在上下文里。

自动压缩会把已调用的 skill 带过来,但有 token 预算:

  • 每个 skill 保留最近一次调用的前 5000 token
  • 所有重新附加的 skill 共享 25000 token 的总预算
  • 从最近调用的 skill 开始填这个预算

所以一个会话里调用了很多 skill 的话,早期的可能在压缩后被完全丢掉。

如果一个 skill 在第一次回复之后好像不再影响行为了,内容通常还在,是模型选了其他工具或路径。强化 description 和指令让模型继续偏向它,或者用 hook 做确定性的强制。skill 很大、或者之后又调用了好几个别的,压缩后重新调用一次恢复完整内容。

!`<command>` 语法在 skill 内容送给 Claude 之前执行 shell 命令,输出替换占位符。

---
name: summarize-changes
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullet points, then list any risks.

这是预处理,不是 Claude 执行的东西。Claude 只看到最终结果。

这个机制的价值在于「让 skill 的输出扎根于真实状态」。上面这个 skill 拿到的是当前工作树的真实 diff,不是 Claude 从打开的文件里猜的。

规则细节:

  • 替换只对原始文件跑一遍。命令输出作为纯文本插入,不会被再次扫描寻找新的占位符,所以一个命令无法产出一个占位符让后续轮次展开。
  • 内联形式只在 ! 出现在行首或紧跟空白时被识别。! 跟在其他字符后面(如 KEY=!cmd“)时占位符保持字面文本,命令不运行。
  • 多行命令用 ```! 开头的围栏代码块。

组织侧可以关掉:"disableSkillShellExecution": true。命令会被替换成 [shell command execution disabled by policy]。内置和 managed skill 不受影响。

变量说明
$ARGUMENTS全部参数。内容里没有它时,参数以 ARGUMENTS: <value> 追加在末尾
$ARGUMENTS[N]按 0 起始的下标取单个参数
$N$ARGUMENTS[N] 的简写
$namearguments frontmatter 里声明的命名参数
${CLAUDE_SESSION_ID}当前会话 ID
${CLAUDE_EFFORT}当前 effort level
${CLAUDE_SKILL_DIR}包含 SKILL.md 的目录
${CLAUDE_PROJECT_DIR}项目根目录

${CLAUDE_SKILL_DIR} 有一个很实用的用法:它在 skill 正文和 allowed-tools都会被替换,所以能让 skill 无提示地运行自己捆绑的脚本:

---
name: render-chart
description: Render a chart from a CSV file
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *)
---
Run `${CLAUDE_SKILL_DIR}/scripts/render.sh <csv-file>` to render the chart.

两处的 ${CLAUDE_SKILL_DIR} 展开成同一个目录,于是 allow 规则精确匹配 skill 正文让 Claude 运行的那条命令,脚本无需权限提示就能跑。

这个替换需要 v2.1.129 或更高版本。旧版本上规则保持字面 ${CLAUDE_SKILL_DIR} 字符串,永远匹配不上,命令仍然要确认权限。

下标参数用 shell 风格的引号规则,多词值要用引号包成一个参数:/my-skill "hello world" second$0 展开成 hello world$1 展开成 second

要写字面的 $ 加数字,用反斜杠转义:\$1.00

一条消息开头可以叠几个 skill。敲 /write-tests /fix-issue 123 会加载两个 skill,把尾部文本 123 作为 $ARGUMENTS 传给每一个。

Claude Code 展开第一个 skill 加后面最多五个。展开在第一个不是「内联 user-invocable skill」的 token 处停止——所以 fork 成 subagent 运行的 skill(如 /code-review)、或者参数本身可能以斜杠命令开头的 skill(如 /loop)也会终止展开。那个 token 及之后的一切成为所有已展开 skill 的参数文本。

skill 目录里可以放多个文件:

my-skill/
├── SKILL.md # 必需,概览与导航
├── reference.md # 详细 API 文档,需要时才加载
├── examples.md # 用法示例
└── scripts/
└── helper.py # 脚本,被执行而不是被加载

在 SKILL.md 里引用这些文件,让 Claude 知道每个文件装了什么、什么时候该加载:

## Additional resources
- For complete API details, see [reference.md](reference.md)
- For usage examples, see [examples.md](examples.md)

SKILL.md 控制在 500 行以内,详细参考资料移到单独文件。

context: fork 让 skill 在隔离上下文里运行,skill 内容成为驱动 subagent 的提示词。它拿不到你的对话历史。

fork 出的 subagent 默认在后台运行:你继续工作,结果完成时回到对话。设 background: false 则在调用它的那一轮等结果。

几种情况下 Claude Code 会等结果,即使没设 background: false

  • 非交互模式(-p 标志或 Agent SDK)
  • 设了 CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1
  • 上一次同名 skill 的调用还在运行时又调用它
  • 定时任务以这个 skill 为提示词触发

context: fork 只对有明确指令的 skill 有意义。如果 skill 装的是「用这些 API 约定」这类指导而没有任务,subagent 收到指导但没有可执行的提示词,会没有实质产出就返回。

skill 和 subagent 有两个配合方向:

方式系统提示来自任务是另外加载
skill 配 context: forkagent 类型SKILL.md 内容CLAUDE.md(agent 是 Explore 或 Plan 时除外)
subagent 配 skills 字段subagent 的 markdown 正文Claude 的委派消息预加载的 skills + CLAUDE.md

内置的 Explore 和 Plan agent 跳过 CLAUDE.md 和 git status 以保持上下文小,所以用 agent: Explore 的 fork skill 只看到 SKILL.md 内容和 agent 自己的系统提示。

三种方式。

全部禁用,在 /permissions 里 deny Skill 工具:

Skill

按名字允许或拒绝:

# 只允许特定 skill
Skill(commit)
Skill(review-pr *)
# 拒绝特定 skill
Skill(deploy *)

Skill(name) 精确匹配,Skill(name *) 前缀匹配加任意参数。

第三种是在 frontmatter 里加 disable-model-invocation: true,这会把 skill 完全从 Claude 的上下文里移除。

skillOverrides 从设置控制可见性,不改 skill 自己的 frontmatter。适合那些你不想编辑的 skill,比如提交在共享仓库里的:

列给 Claude/ 菜单
"on"名字和描述
"name-only"只有名字
"user-invocable-only"隐藏
"off"隐藏隐藏
{
"skillOverrides": {
"legacy-context": "name-only",
"deploy": "off"
}
}

/skills 菜单会帮你写:高亮一个 skill 按空格循环状态,回车保存到 .claude/settings.local.json

不在 skillOverrides 里的 skill 视为 "on"。插件 skill 不受 skillOverrides 影响,那些用 /plugin 管。

Claude Code 把一份 skill 名字和描述的列表加载进上下文,让 Claude 知道有什么可用。列表总是包含每个 skill 的名字,但 skill 很多时描述会被缩短以适应字符预算,可能剥掉 Claude 匹配你请求所需的关键词。

预算按模型上下文窗口的 1% 缩放。列表溢出时,Claude Code 从你调用最少的 skill 开始丢描述,所以最常用的保留完整文本。

诊断和调整:

  • /doctor 给出列表上下文成本的估计和最大贡献者
  • /context 的 Skills 行报告预算应用之后的列表大小,与模型实际收到的一致
  • 提高预算:skillListingBudgetFraction(如 0.02 表示 2%)或 SLASH_COMMAND_TOOL_CHAR_BUDGET 环境变量设固定字符数
  • 给其他 skill 腾预算:把低优先级的设成 "name-only"
  • 从源头精简:把关键用例写在最前面,每条的合并文本上限 1536 字符(可用 skillListingMaxDescChars 调)
  1. 检查 description 里有没有用户会自然说出的关键词
  2. 问一句「What skills are available?」确认它在列表里
  3. 把请求改得更贴近 description
  4. 如果它是 user-invocable,直接用 /skill-name 调用

如果 frontmatter YAML 格式坏了,Claude Code 会加载 skill 正文但元数据为空——所以 /skill-name 仍然能用,但 Claude 没有描述可以匹配。用 --debug 看解析错误。

反过来触发太频繁:把 description 写得更具体,或者加 disable-model-invocation: true

看到 skill 触发,只说明 Claude 找到了它,不说明它做了你想要的事。要判断 skill 是否有效,分开测两件事:

  1. Claude 在该触发的提示上是否调用了它
  2. 调用时输出是否符合预期

两者的检验方法都是基线对比:收集几个真实提示,每个在干净会话里跑两次——一次有这个 skill、一次禁用它,对比结果。

干净会话很重要,因为你在写 skill 时留下的上下文会掩盖书面指令里的缺口。

官方的 skill-creator 插件把这个对比循环自动化了:

/plugin install skill-creator@claude-plugins-official

它会把测试用例存在 skill 目录的 evals/evals.json、每个用例开一个 subagent 保证干净上下文、记录 token 数和耗时、把「有 skill vs 没 skill」的通过率与开销聚合到 benchmark.json,还能对两个版本做盲测 A/B。

  • 写一个用 !`git diff HEAD` 注入真实 diff 的 skill,然后对比它和「直接问 Claude 我改了什么」的回答质量。动态注入的价值在这个对比里最明显。
  • 给一个有副作用的 skill(部署、发消息)加 disable-model-invocation: true,然后试着让 Claude 自己触发它,确认它拒绝。
  • 在一个会话里连续调用五六个不同 skill,然后触发一次压缩,用 /context 看哪些被保留了。这能让 25000 token 的重新附加预算从数字变成体感。
  • hooks frontmatter 字段的完整配置格式。文档指向另一页(Hooks in skills and agents),本文未核实。
  • paths frontmatter 在 skill 上的行为与 .claude/rules/ 上的是否完全一致。文档说用同样的格式,但触发时机是否相同没有明确说明。
  • 压缩后重新附加时「前 5000 token」的截断边界。是按 token 硬切还是按段落边界,未见说明。
  • effort 字段各档位在不同模型上的可用性。文档说取决于模型,没有给出对应表。
这一节有错或讲不清? 提个 Issue 直接改文档 请作者催更