Claude Code 的 Skills 系统
很多人把 Skill 想简单了
第一次接触 Claude Code 的人,通常会把 Skill 理解成:
- 一段写好的 Prompt
- 一个 Slash Command 别名
- 或者一份放在仓库里的说明文档
这些理解都沾边,但都不完整。
从源码看,Claude Code 里的 Skill 更准确的定义是:
一套可以被系统加载、被模型发现、再由
SkillTool正式执行的技能模块。
也就是说,Skill 不是普通文档。
它在 Claude Code 里有自己的加载链路、发现链路和执行链路。
先从直觉上理解:Skill 到底解决什么问题
Claude Code 已经有很多工具了,为什么还要单独做一套 Skills?
因为很多复杂任务的问题,并不是“工具不够”,而是模型不知道:
- 这类任务应该按什么步骤做
- 这个团队平时遵循什么规则
- 某个场景下应该优先用哪些工具
- 哪些坑要提前避开
举个很典型的例子:
- “帮我提交代码” 不是一个简单命令
- “做一轮安全审计” 也不是一个单步动作
- “去线上排查问题” 更不是一句 Prompt 就能稳定完成的事
这些任务真正需要的是一套流程。
而 Skill 干的事情,就是把这套流程固化下来。
一个 Skill 文件里通常会写什么
Skill 最常见的载体就是 SKILL.md。
Claude Code 会先读 frontmatter,再读正文内容。
frontmatter 里常见的东西有:
descriptionwhenToUseallowedToolsmodelcontexthooks
正文部分则是这项技能真正的操作说明。
你可以把它理解成:
- frontmatter 负责告诉系统“这是什么技能”
- 正文负责告诉模型“这个技能具体要怎么做”
第一步:Claude Code 怎么加载 Skills
源码里真正负责这件事的核心文件是 skills/loadSkillsDir.ts。
它做的事情并不复杂,但很工程化:
- 去多个目录扫描
skills - 找到每个 Skill 对应的
SKILL.md - 解析 frontmatter
- 把它转成内部的
Command - 放进当前 session 的可用 Skill 列表
源码里能看到非常直接的一组函数:
parseSkillFrontmatterFields(...)
createSkillCommand(...)
getSkillDirCommands(...)
这几个名字已经把逻辑说得很清楚了:
- 先解析字段
- 再创建 Skill 对应的 command
- 最后统一返回这批 command
所以 Skill 在 Claude Code 内部的身份,不是“额外附件”,而是命令系统里的一等成员。
Skills 从哪些地方加载
getSkillDirCommands() 会从多个位置收集 Skills。
你不用死记具体路径,理解优先级就够了:
- 平台或策略下发的 Skills
- 用户自己的 Skills
- 项目里的
.claude/skills - 额外指定目录里的 Skills
- 兼容旧版
commands目录里的 Skill 形式
这意味着 Skill 既可以是:
- 官方内置经验
- 团队共享经验
- 个人工作流
- 某个项目专属规则
这也是它比“普通 Prompt 收藏夹”强很多的地方。
它从一开始就不是只为个人设计的,而是带着组织级复用能力去做的。
Claude Code 不会一上来把所有 Skill 正文都塞给模型
这里有个特别关键的设计。
很多人会以为:既然 Skills 已经加载进来了,那 Claude Code 每轮都把这些 Skill 全发给模型不就好了?
它没有这么做。
更合理的做法是:
- 先把 Skill 的名字、简介、适用场景暴露出来
- 让模型知道“有哪些技能可用”
- 等模型真的决定调用某个 Skill,再把完整内容展开进去
这就是“发现”和“执行”分离。
源码里这一层的痕迹很明显:
skill_discoverydiscoveredSkillNamesinvoked_skills
utils/messages.ts 里甚至直接把 skill discovery 变成一条系统提醒:
`Skills relevant to your task:\n\n${lines.join('\n')}\n\n` +
`These skills encode project-specific conventions. ` +
`Invoke via Skill("<name>") for complete instructions.`
这段提示说明得非常直接:
先告诉模型“这些 Skills 和你当前任务相关”,真正完整的说明,要等调用
Skill(...)之后再展开。
第二步:模型是怎么“发现” Skill 的
你可以把这一层理解成“推荐技能”。
当 Claude Code 判断当前任务和某些 Skill 匹配时,它不会直接强塞一整篇 Skill 文本,而是先给模型一个简短提示:
- 这个 Skill 叫什么
- 它适合解决什么问题
- 如果需要,应该通过
SkillTool去调用它
这样做有两个好处:
- 节省上下文
- 让模型只在真正需要时才进入某个 Skill 的详细流程
这就是 Claude Code 的一贯风格:
先暴露能力概览,再按需展开细节。
第三步:真正执行 Skill 的是 SkillTool
Skill 最终不是自动运行的。
真正负责执行它的,是 tools/SkillTool/SkillTool.ts。
这个工具干的事情大概可以概括成五步:
- 根据名字找到对应 Skill
- 校验它是不是合法的 prompt 类 command
- 检查权限规则
- 展开 Skill 的完整内容
- 决定是 inline 执行还是 fork 执行
源码里最关键的两个点非常清楚:
async function getAllCommands(context: ToolUseContext): Promise<Command[]> {
const mcpSkills = context
.getAppState()
.mcp.commands.filter(
cmd => cmd.type === 'prompt' && cmd.loadedFrom === 'mcp',
)
...
}
这说明 SkillTool 找 Skill 时,不只看本地 Skill,连 MCP 暴露出来的 Skill 也一起纳入查找。
另一个关键点是:
async function executeForkedSkill(...) {
...
for await (const message of runAgent({...})) {
...
}
}
这说明 Skill 真正复杂起来时,不一定在主线程里直接跑,完全可以 fork 成一个子 Agent 去执行。
inline 和 fork,到底区别在哪
这是 Skills 系统里最值得记住的一个点。
inline
默认模式通常更接近这个。
意思是:
- Skill 内容直接展开到当前主流程
- 主 Agent 看完这套说明后,继续在当前上下文里往下做
适合:
- 比较轻量的流程
- 只是想补一段规则
- 不需要额外隔离上下文的任务
fork
这是 Claude Code Skills 真正有意思的地方。
一旦某个 Skill 比较复杂,它可以不污染当前主流程,而是:
- 开一个新的子 Agent
- 把 Skill 流程交给子 Agent 跑
- 子 Agent 跑完再把结果返回来
这样做的好处非常实际:
- 主流程上下文更干净
- 复杂技能更容易隔离
- 某个 Skill 跑偏了,不会把整个主线程带乱
所以你会发现:
Claude Code 里的 Skill,不只是“更长的 Prompt”,而是“可以在需要时开子 Agent 执行的一段流程”。
Skills 和普通 Prompt 的区别
这个问题很关键。
如果只是临时写一段 Prompt,它的特点是:
- 这次会话里有效
- 下次还得重新写
- 系统并不知道它是什么能力
但 Skill 不一样。
Skill 是:
- 可保存的
- 可复用的
- 可被系统加载的
- 可被模型发现的
- 可被
SkillTool正式执行的
所以你可以把它理解成:
- Prompt 是一次性说明
- Skill 是固化下来的流程模块
为什么说 Skills 是 Claude Code 平台化的重要一步
因为 Skill 解决的不是“模型能力不够”,而是“经验不好复用”。
很多团队真正想沉淀的东西并不是一个工具,而是:
- 某类任务应该怎么做
- 某个仓库要遵循什么规范
- 某个流程里先后顺序是什么
- 某种场景下该调用哪些工具
这些东西写成文档,模型不一定会主动遵守。
但做成 Skill,系统就能把它变成一项正式能力。
这就是它最值钱的地方。
小结
一句话总结:
Claude Code 的 Skills,不是几篇 Markdown 提示词,而是一套“先加载、再发现、最后由 SkillTool 执行”的技能运行时。
理解了这一点,你就知道为什么 Skills 在 Claude Code 里不是边角料,而是平台化能力的一部分。