深入 Claude Code 源码:Skill 系统架构全解析
本文基于源码逆向分析,带你走进这套”Prompt 即代码”架构的核心。所有代码片段均经源码逐行核实。
一、核心洞察:Prompt 即代码
Skill 系统的设计哲学可以归结为一句话:
AI 的能力 = Prompt 模板 + 工具集 + 上下文
它本质上是一个 “Prompt 模板 + 元数据”的惰性执行框架。所有技能最终都收敛为一个函数:
async getPromptForCommand( args: string, context: ToolUseContext,): Promise<ContentBlockParam[]>
这意味着三件事:
-
技能定义本身几乎不占内存
-
可以根据参数和上下文动态生成内容
-
支持文件读取、API 调用、条件逻辑
二、架构全景图
整个执行流程分为三个阶段:校验 → 授权 → 执行。
用户输入 /skill-name │ ▼SkillTool.validateInput() - 检查技能是否存在 - 检查是否禁用模型调用 - 检查是否为 prompt 类型 │ ▼SkillTool.checkPermissions() - deny 规则 → allow 规则 - safe-properties 自动放行 - 默认:ask(需用户确认) │ ▼SkillTool.call() → 三种模式 ┌──────────┬──────────┬──────────┐ │ Inline │ Fork │ Remote │ │ (默认) │ (子代理) │ (实验性) │ └──────────┴──────────┴──────────┘
Inline模式把 Prompt 注入当前对话,模型继续思考执行。
Fork模式通过 runAgent()在独立上下文中运行子代理。
Remote模式从云端加载官方策展的 SKILL.md,注入对话。
三、统一的 Command 抽象
所有技能,无论来源,都收敛为同一个 Command类型:
export type Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)
注意这是一个三分支联合类型。其中 PromptCommand是 Skill 系统的核心,承载了技能的全部元数据。
PromptCommand 关键字段
-
type: 'prompt'— 标识这是 Prompt 类技能 -
source— 来源类型:policySettings | userSettings | projectSettings | builtin | mcp | plugin | bundled -
allowedTools— 该技能可使用的工具白名单 -
model— 模型指定(如opus、sonnet、haiku) -
context— 执行模式:'inline'(默认)或'fork'(子代理) -
effort— 思考力度:low | medium | high | max或整数 -
hooks— 技能钩子定义 -
paths— Glob 路径过滤,限定技能激活范围 -
getPromptForCommand()— 核心:返回 Prompt 内容的函数
CommandBase 共享元数据
每个命令都有 name、description、aliases、whenToUse、version等基础字段。
还有一个容易和 source混淆的字段 loadedFrom,它标识加载路径:'commands_DEPRECATED' | 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp'。
技能来源全景
-
bundled— 编译时打包的内置技能(/verify、/simplify等) -
skills+userSettings— 用户级技能,位于~/.claude/skills/ -
skills+projectSettings— 项目级技能,位于.claude/skills/ -
skills+policySettings— 企业策略技能 -
plugin— 插件提供的技能 -
mcp— MCP 协议提供的技能 -
commands_DEPRECATED— 旧版命令目录(已废弃)
对用户来说,所有技能都是 /skill-name,统一的发现机制、执行路径、权限控制。
四、惰性加载机制
内置技能注册
内置技能在启动时通过 initBundledSkills()注册。当前版本包含:
无条件注册(10 个):UpdateConfig、Keybindings、Verify、Debug、LoremIpsum、Skillify、Remember、Simplify、Batch、Stuck
条件注册(依赖 feature flag):
-
feature('KAIROS') || feature('KAIROS_DREAM')→ Dream -
feature('REVIEW_ARTIFACT')→ Hunter -
feature('AGENT_TRIGGERS')→ Loop -
feature('AGENT_TRIGGERS_REMOTE')→ ScheduleRemoteAgents -
feature('BUILDING_CLAUDE_APPS')→ ClaudeApi -
shouldAutoEnableClaudeInChrome()→ ClaudeInChrome -
feature('RUN_SKILL_GENERATOR')→ RunSkillGenerator
每个内置技能调用 registerBundledSkill(definition)将自己注册到全局数组中。
BundledSkillDefinition 类型
除了基础的 name、description,还支持:
-
context: 'inline' | 'fork'— 执行模式 -
agent— fork 时使用的代理类型 -
files: Record<string, string>— 附带的参考文件
当定义了 files时,注册函数会构建惰性释放逻辑:首次调用时才将文件写入磁盘。写入使用 O_EXCL | O_NOFOLLOW标志防止符号链接攻击,目录权限 0o700,文件权限 0o600。
磁盘技能加载
用户技能使用目录格式:skill-name/SKILL.md。不支持 /skills/目录下的裸 .md文件。
~/.claude/skills/ explain/ SKILL.md ← 技能定义 schemas/ ← 可选的附带资源 review/ SKILL.md
SKILL.md 示例:
---description: 我的技能描述when_to_use: 当需要...时使用allowed-tools: BashTool,FileReadToolmodel: claude-sonnet-4-6context: forkeffort: high---# 技能 Prompt 正文
加载流程五步走
getSkillDirCommands函数执行以下步骤:
1. 并发扫描:同时加载 managed(企业)、user(~/.claude/skills)、project(.claude/skills)、additional(--add-dir)和 legacy(commands/)五类目录。
2. 解析 Frontmatter:每个 SKILL.md调用 parseFrontmatter+ parseSkillFrontmatterFields。
3. 文件身份去重:通过 realpath解析符号链接,确保同一文件不重复加载。
4. 条件技能分离:含 paths前置条件的技能存入 conditionalSkills映射,等到模型触碰到匹配文件时才激活。
5. 注册 MCP 构建器:通过 registerMCPSkillBuilders暴露 createSkillCommand给 MCP 模块。
注意:Gitignore 过滤发生在动态发现阶段(
discoverSkillDirsForPaths),不在主加载流程中。
五、三种执行模式
Inline 模式(默认)
技能内容直接注入当前对话。流程:
-
processPromptSlashCommand()调用getPromptForCommand(args) -
返回的 Prompt 包装为
newMessages,带sourceToolUseID标记 -
contextModifier设置allowedTools+model+effort -
模型在当前上下文中继续思考执行
Fork 模式(子代理)
复杂技能在独立上下文中执行。核心函数 executeForkedSkill的完整签名:
async function executeForkedSkill( command: Command & { type: 'prompt' }, commandName: string, args: string | undefined, context: ToolUseContext, canUseTool: CanUseToolFn, parentMessage: AssistantMessage, onProgress?: ToolCallProgress<Progress>,): Promise<ToolResult<Output>>
关键步骤:
-
prepareForkedCommandContext()准备独立的 AppState 和 Prompt 消息 -
合并技能的
effort设置到agentDefinition -
runAgent()启动子代理,通过onProgress向父级报告进度 -
extractResultText()从子代理消息中提取结果 -
clearInvokedSkillsForAgent()释放资源
Remote 模式(实验性)
远程技能通过 _canonical_<slug>前缀标识,从 AKI/GCS 加载 SKILL.md(带本地缓存)。
与本地技能的区别:不经过 processPromptSlashCommand的 !command/ $ARGUMENTS展开(安全限制),直接以声明式 Markdown 注入对话。
三种模式对比
-
Inline:共享当前对话上下文,Token 计入当前对话,支持
!Shell 语法,适合简单指令扩展 -
Fork:独立上下文和 Token 预算,可独立指定模型,适合复杂任务和长对话
-
Remote:共享当前对话,禁止 Shell 命令执行,适合官方策展技能
六、Frontmatter 解析的两阶段策略
Frontmatter 中经常需要 glob 模式,如 **/*.{ts,tsx},但花括号与 YAML 语法冲突。
parseFrontmatter的解决方案是两阶段 try-catch:
阶段一:直接用 parseYaml解析原始文本。
阶段二:如果失败,调用 quoteProblematicValues自动转义后重试。
quoteProblematicValues逐行检查 key: value格式,如果 value 包含 {}[]&#!|>%@或 : 等 YAML 特殊字符,自动加双引号。已有引号的跳过。
Frontmatter 支持的完整字段
-
description— 技能描述 -
when_to_use— 使用场景说明 -
allowed-tools— 可用工具(字符串或数组) -
model— 模型指定,'inherit'继承父模型 -
context—'inline'或'fork' -
agent— fork 时使用的代理类型 -
effort— 思考力度(low/medium/high/max 或整数) -
hooks— 钩子配置 -
paths— Glob 路径过滤(字符串或数组) -
user-invocable— 用户是否可通过/skill-name调用 -
argument-hint— 参数提示文本 -
version— 版本号 -
shell—!代码块的 Shell 类型(bash或powershell) -
skills— 逗号分隔的预加载技能名(仅用于 agent) -
hide-from-slash-command-tool— 是否对 SlashCommand 工具隐藏 -
type— 内存类型(仅用于 memory 文件)
七、权限与工具限制
静态限制
技能通过 Frontmatter 声明可用工具:
allowed-tools: BashTool,FileReadTool,GrepTool
动态权限:五级判断链
checkPermissions按优先级递减依次判断:
① deny 规则→ 命中则阻止执行
② 远程技能 auto-allow(实验性,仅内部用户)
③ allow 规则→ 命中则放行
④ safe-properties 自动放行→ 如果技能只有安全属性,直接 auto-allow
⑤ 默认:ask→ 需用户确认
规则匹配支持两种模式
精确匹配:规则 review-pr匹配技能名 review-pr。
前缀匹配:规则 review:*匹配所有以 review开头的技能。
前导斜杠 /会被自动去除,确保 /review-pr和 review-pr匹配同一条规则。
Safe-properties 白名单机制
这是一个巧妙的安全设计:SAFE_SKILL_PROPERTIES集合列出了所有”安全”属性名(type、name、description、model、effort等)。
只要技能的所有非空属性都在集合内,就自动放行。如果技能包含了 hooks、allowedTools等不在白名单中的属性,则需要用户授权。
关键:这确保了未来新增的属性默认需要授权,安全设计上属于 opt-in 而非 opt-out。
八、钩子系统
28 种事件类型
技能钩子基于会话级事件系统,支持丰富的生命周期事件(定义在 coreSchemas.ts):
工具相关:PreToolUse、PostToolUse、PostToolUseFailure
会话生命周期:SessionStart、SessionEnd、Stop、StopFailure
子代理:SubagentStart、SubagentStop
上下文管理:PreCompact、PostCompact
权限:PermissionRequest、PermissionDenied
用户交互:UserPromptSubmit、Notification、Elicitation、ElicitationResult
其他:Setup、ConfigChange、CwdChanged、FileChanged、WorktreeCreate、WorktreeRemove等
Frontmatter 中定义钩子
hooks: PreToolUse: - matcher: BashTool hooks: - type: command command: "echo 'About to run bash'" PostToolUse: - matcher: "" hooks: - type: command command: "echo 'Tool completed'" Stop: - matcher: "" hooks: - type: command command: "echo 'Cleanup'" once: true
三种钩子类型
-
command— 执行 Shell 命令 -
prompt— 注入 Prompt 内容 -
agent— 启动子代理
钩子注册机制
registerSkillHooks将钩子注册到会话作用域。关键特性:
-
once: true的钩子在首次成功执行后自动移除(通过removeSessionHook) -
skillRoot参数设置CLAUDE_PLUGIN_ROOT环境变量,供钩子脚本引用 -
每个钩子的执行都有独立的错误捕获,钩子错误不影响主流程
对于代理中的 Stop钩子,registerFrontmatterHooks会自动将其转换为 SubagentStop事件——因为子代理完成时触发的是 SubagentStop而非 Stop。
九、模型路由
技能指定模型分两步完成。
加载时解析
在 parseSkillFrontmatterFields中处理 Frontmatter:
-
model: inherit→ 返回undefined,运行时继承当前模型 -
model: opus→ 调用parseUserSpecifiedModel解析为完整模型名 -
不设置 → 同 inherit
执行时应用
resolveSkillModelOverride处理一个微妙的边界情况:1M 上下文窗口的继承。
如果当前会话使用 opus[1m](1M token 上下文),而技能只指定了 opus,函数会自动补上 [1m]后缀,避免上下文窗口从 1M 意外降到 200K。
export function resolveSkillModelOverride( skillModel: string, currentModel: string,): string { if (has1mContext(skillModel) || !has1mContext(currentModel)) { return skillModel } if (modelSupports1M(parseUserSpecifiedModel(skillModel))) { return skillModel + '[1m]' } return skillModel}
使用场景举例:/lorem-ipsum用 haiku,/simplify用 opus,一般任务继承当前模型。
十、遥测与分析
每次技能调用都会触发 tengu_skill_tool_invocation事件。
所有用户可见的字段
-
command_name— sanitized 后的技能名(内置/bundled 保留原名,其余为'custom') -
execution_context—'inline' | 'fork' | 'remote' -
invocation_trigger—'nested-skill'(嵌套调用)或'claude-proactive'(主动调用) -
query_depth— 嵌套深度 -
was_discovered— 是否通过 DiscoverSkills 发现
仅内部用户的字段
process.env.USER_TYPE === 'ant'时额外发送:skill_name(未脱敏)、skill_source、skill_loaded_from、skill_kind。
其他遥测事件
-
tengu_skill_loaded— 会话启动时记录已加载技能 -
tengu_skill_file_changed— 技能文件变更检测 -
tengu_dynamic_skills_changed— 动态技能发现 -
tengu_skill_tool_slash_prefix— 用户在技能名前加了/前缀 -
tengu_skill_descriptions_truncated— 技能描述因 token 限制被截断 -
tengu_skill_improvement_detected— 技能改进建议
十一、为什么感觉”顺滑”?
-
快速加载— 惰性 Prompt 生成,技能定义不占内存。启动快,技能多也不卡。
-
一致性— 统一 Command 抽象,三种变体收敛一个类型。所有技能使用方式一致。
-
灵活执行— Inline/Fork/Remote 三模式。简单技能快,复杂技能强。
-
易扩展— Frontmatter 声明式 + 目录约定。写个 SKILL.md 就是技能。
-
自动发现— 多层级目录扫描 + 条件激活。技能自动出现在正确位置。
-
安全可控— deny/allow 规则 + safe-properties 自动放行。既安全又不阻碍使用。
-
可编排— 会话级钩子,28+ 事件类型。支持复杂业务逻辑。
-
性价比— 模型路由 + 1M 上下文继承。不同技能用不同模型。
十二、实战:编写一个 Skill
技能使用目录格式。在 ~/.claude/skills/下创建以技能名命名的目录,目录内放 SKILL.md:
mkdir -p ~/.claude/skills/explain
~/.claude/skills/explain/SKILL.md内容:
---description: 解释代码的工作原理when_to_use: 当用户想理解一段代码的逻辑时allowed-tools: FileReadTool,GrepToolcontext: inline---# 代码解释技能## Goal帮助用户理解代码的工作原理。## Steps1. 使用 FileReadTool 读取目标文件2. 分析代码结构、数据流和控制流3. 用清晰的中文解释核心逻辑4. 针对用户的具体问题补充说明
使用:/explain src/utils/auth.ts
结语
Claude Code 的 Skill 系统之所以”顺滑”,遵循了几个核心原则:
-
简单优于复杂— 技能定义只是一个 Markdown 文件
-
约定优于配置— 统一的目录结构
skill-name/SKILL.md -
惰性优于急切— Prompt 只在需要时生成
-
组合优于继承— 通过钩子、工具、模型的组合实现灵活性
-
安全默认— 新属性默认需要授权,白名单明确放行
这套架构的核心洞察是:在 AI 时代,代码的本质是 Prompt。
Skill 系统只是提供了一个优雅的机制来管理、组合和执行这些 Prompt。理解了这一点,你也就理解了 Claude Code 的灵魂。
附录:关键文件索引
-
types/command.ts— Command / PromptCommand / CommandBase 类型定义 -
skills/bundledSkills.ts— BundledSkillDefinition + registerBundledSkill -
skills/bundled/index.ts— initBundledSkills 启动注册 -
skills/loadSkillsDir.ts— 磁盘技能加载器 + 动态发现 + 条件激活 -
skills/mcpSkills.ts— MCP 技能加载 -
tools/SkillTool/SkillTool.ts— SkillTool 核心逻辑 -
tools/SkillTool/prompt.ts— 技能描述 Prompt 生成 -
utils/frontmatterParser.ts— Frontmatter 两阶段解析 -
utils/hooks/registerSkillHooks.ts— 技能钩子注册 -
utils/hooks/registerFrontmatterHooks.ts— Frontmatter 钩子注册 -
utils/model/model.ts— resolveSkillModelOverride -
utils/forkedAgent.ts— prepareForkedCommandContext -
services/skillSearch/— 远程技能搜索与加载 -
entrypoints/sdk/coreSchemas.ts— HOOK_EVENTS 事件定义
本文基于 Claude Code 开源源码分析,版本参考 2026 年 4 月。所有代码片段均经源码逐行核实。
夜雨聆风