Claude Code skills:SKILL.md 全字段、谁能调用它、以及为什么内容进了上下文就不走了
什么时候该写 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 自动加载的能力。
存放位置与覆盖顺序
Section titled “存放位置与覆盖顺序”| 位置 | 路径 | 作用范围 |
|---|---|---|
| 企业 | 见 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 何时可用
Section titled “子目录里的 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 显式运行嵌套变体。
frontmatter 全字段
Section titled “frontmatter 全字段”所有字段都是可选的,只有 description 是推荐的——Claude 靠它判断什么时候用这个 skill。
布尔字段接受 yes、no、on、off、1、0(任意大小写),以及 true、false。v2.1.218 之前只认后两个。
| 字段 | 说明 |
|---|---|
name | 列表里显示的名字。默认取目录名。个人和项目 skill 里它只影响显示,命令名仍来自目录名 |
description | 做什么、何时用。省略时取 markdown 正文第一段 |
when_to_use | 补充触发场景,追加在 description 后面 |
argument-hint | 自动补全时显示的参数提示,如 [issue-number] |
arguments | 命名位置参数,用于 $name 替换。名字按顺序映射到参数位置 |
disable-model-invocation | true 时只有你能调用 |
user-invocable | false 时从 / 菜单隐藏,只有 Claude 能调用 |
allowed-tools | 调用这个 skill 的那一轮里可以不询问就使用的工具 |
disallowed-tools | skill 活跃期间从 Claude 可用工具池里移除的工具 |
model | 这个 skill 活跃时用的模型。覆盖只在当前轮次生效,不写进设置 |
effort | 这个 skill 活跃时的 effort level |
context | 设为 fork 在 fork 出的 subagent 上下文里运行 |
agent | context: fork 时用哪种 subagent 类型 |
background | 仅在 context: fork 时有效。设 false 则在调用它的那一轮等结果。默认 true |
hooks | 限定在这个 skill 生命周期内的 hook |
paths | glob 模式,限制何时自动激活这个 skill |
shell | !command“ 块用哪个 shell,bash(默认)或 powershell |
description 和 when_to_use 的合并文本在 skill 列表里被截断到 1536 字符,所以把最关键的用例写在最前面。
两个方向的调用权
Section titled “两个方向的调用权”默认你和 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 很大、或者之后又调用了好几个别的,压缩后重新调用一次恢复完整内容。
动态上下文注入
Section titled “动态上下文注入”!`<command>` 语法在 skill 内容送给 Claude 之前执行 shell 命令,输出替换占位符。
---name: summarize-changesdescription: 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] 的简写 |
$name | arguments 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-chartdescription: Render a chart from a CSV fileallowed-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 行以内,详细参考资料移到单独文件。
在 subagent 里跑 skill
Section titled “在 subagent 里跑 skill”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: fork | agent 类型 | 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 自己的系统提示。
限制 Claude 能调用哪些 skill
Section titled “限制 Claude 能调用哪些 skill”三种方式。
全部禁用,在 /permissions 里 deny Skill 工具:
Skill按名字允许或拒绝:
# 只允许特定 skillSkill(commit)Skill(review-pr *)
# 拒绝特定 skillSkill(deploy *)Skill(name) 精确匹配,Skill(name *) 前缀匹配加任意参数。
第三种是在 frontmatter 里加 disable-model-invocation: true,这会把 skill 完全从 Claude 的上下文里移除。
从设置里覆盖可见性
Section titled “从设置里覆盖可见性”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 管。
描述被截断的问题
Section titled “描述被截断的问题”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调)
skill 不触发怎么查
Section titled “skill 不触发怎么查”- 检查 description 里有没有用户会自然说出的关键词
- 问一句「What skills are available?」确认它在列表里
- 把请求改得更贴近 description
- 如果它是 user-invocable,直接用
/skill-name调用
如果 frontmatter YAML 格式坏了,Claude Code 会加载 skill 正文但元数据为空——所以 /skill-name 仍然能用,但 Claude 没有描述可以匹配。用 --debug 看解析错误。
反过来触发太频繁:把 description 写得更具体,或者加 disable-model-invocation: true。
怎么知道 skill 真的有用
Section titled “怎么知道 skill 真的有用”看到 skill 触发,只说明 Claude 找到了它,不说明它做了你想要的事。要判断 skill 是否有效,分开测两件事:
- Claude 在该触发的提示上是否调用了它
- 调用时输出是否符合预期
两者的检验方法都是基线对比:收集几个真实提示,每个在干净会话里跑两次——一次有这个 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 的重新附加预算从数字变成体感。
还没确认的点
Section titled “还没确认的点”hooksfrontmatter 字段的完整配置格式。文档指向另一页(Hooks in skills and agents),本文未核实。pathsfrontmatter 在 skill 上的行为与.claude/rules/上的是否完全一致。文档说用同样的格式,但触发时机是否相同没有明确说明。- 压缩后重新附加时「前 5000 token」的截断边界。是按 token 硬切还是按段落边界,未见说明。
effort字段各档位在不同模型上的可用性。文档说取决于模型,没有给出对应表。