乐于分享
好东西不私藏

深入 Claude Code 源码:Skill 系统架构全解析

深入 Claude Code 源码:Skill 系统架构全解析

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— 模型指定(如 opussonnethaiku

  • context— 执行模式:'inline'(默认)或 'fork'(子代理)

  • effort— 思考力度:low | medium | high | max或整数

  • hooks— 技能钩子定义

  • paths— Glob 路径过滤,限定技能激活范围

  • getPromptForCommand()— 核心:返回 Prompt 内容的函数

CommandBase 共享元数据

每个命令都有 namedescriptionaliaseswhenToUseversion等基础字段。

还有一个容易和 source混淆的字段 loadedFrom,它标识加载路径:'commands_DEPRECATED' | 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp'

技能来源全景

  • bundled— 编译时打包的内置技能(/verify/simplify等)

  • skillsuserSettings— 用户级技能,位于 ~/.claude/skills/

  • skillsprojectSettings— 项目级技能,位于 .claude/skills/

  • skillspolicySettings— 企业策略技能

  • 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 类型

除了基础的 namedescription,还支持:

  • 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调用 parseFrontmatterparseSkillFrontmatterFields

3. 文件身份去重:通过 realpath解析符号链接,确保同一文件不重复加载。

4. 条件技能分离:含 paths前置条件的技能存入 conditionalSkills映射,等到模型触碰到匹配文件时才激活。

5. 注册 MCP 构建器:通过 registerMCPSkillBuilders暴露 createSkillCommand给 MCP 模块。

注意:Gitignore 过滤发生在动态发现阶段(discoverSkillDirsForPaths),不在主加载流程中。


五、三种执行模式

Inline 模式(默认)

技能内容直接注入当前对话。流程:

  1. processPromptSlashCommand()调用 getPromptForCommand(args)

  2. 返回的 Prompt 包装为 newMessages,带 sourceToolUseID标记

  3. contextModifier设置 allowedToolsmodeleffort

  4. 模型在当前上下文中继续思考执行

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>>

关键步骤:

  1. prepareForkedCommandContext()准备独立的 AppState 和 Prompt 消息

  2. 合并技能的 effort设置到 agentDefinition

  3. runAgent()启动子代理,通过 onProgress向父级报告进度

  4. extractResultText()从子代理消息中提取结果

  5. 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集合列出了所有”安全”属性名(typenamedescriptionmodeleffort等)。

只要技能的所有非空属性都在集合内,就自动放行。如果技能包含了 hooksallowedTools等不在白名单中的属性,则需要用户授权。

关键:这确保了未来新增的属性默认需要授权,安全设计上属于 opt-in 而非 opt-out。


八、钩子系统

28 种事件类型

技能钩子基于会话级事件系统,支持丰富的生命周期事件(定义在 coreSchemas.ts):

工具相关PreToolUsePostToolUsePostToolUseFailure

会话生命周期SessionStartSessionEndStopStopFailure

子代理SubagentStartSubagentStop

上下文管理PreCompactPostCompact

权限PermissionRequestPermissionDenied

用户交互UserPromptSubmitNotificationElicitationElicitationResult

其他SetupConfigChangeCwdChangedFileChangedWorktreeCreateWorktreeRemove

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_sourceskill_loaded_fromskill_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 系统之所以”顺滑”,遵循了几个核心原则:

  1. 简单优于复杂— 技能定义只是一个 Markdown 文件

  2. 约定优于配置— 统一的目录结构 skill-name/SKILL.md

  3. 惰性优于急切— Prompt 只在需要时生成

  4. 组合优于继承— 通过钩子、工具、模型的组合实现灵活性

  5. 安全默认— 新属性默认需要授权,白名单明确放行

这套架构的核心洞察是:在 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 月。所有代码片段均经源码逐行核实。