Skip to content

Instantly share code, notes, and snippets.

@kaichen
Created January 23, 2026 03:12
Show Gist options
  • Select an option

  • Save kaichen/46282b5d74617ae31853526a811eee94 to your computer and use it in GitHub Desktop.

Select an option

Save kaichen/46282b5d74617ae31853526a811eee94 to your computer and use it in GitHub Desktop.
Claude Code Skills 加载机制详解 | How Claude Code Loads Skills

Claude Code Skills 加载机制详解

深入分析 Claude Code CLI 如何发现、加载和激活 skills。基于源代码 package/cli.js 的实现研究。

📋 目录

  1. Skills 发现(Discovery Phase)
  2. Skills 加载(Load Phase)
  3. Frontmatter 解析
  4. 重复处理(Deduplication)
  5. Skills 激活(Activation)
  6. 优先级和覆盖规则
  7. 实战示例
  8. 关键代码位置

1. Skills 发现(Discovery Phase)

Claude Code 启动时扫描三个位置的 skills,按优先级从高到低:

# 优先级:
1. Managed Settings  → /Library/Application Support/ClaudeCode/skills/
2. User Settings     → ~/.claude/skills/
3. Project Settings  → .claude/skills/ (项目根目录)

关键代码 (源代码第 256530 行):

let K = S4A(T8(), "skills"),              // managed
    q = S4A(Wf(), ".claude", "skills"),   // user
    Y = U86("skills", A);                 // project

let [z, w, H] = await Promise.all([
    fw1(q, "policySettings"),             // 扫描 managed
    w$("userSettings") ? fw1(K, "userSettings") : ...,
    w$("projectSettings") ? ... fw1(Z, "projectSettings") : ...
])

发现流程

┌─────────────────────────────────────────┐
│ 1. 扫描三个 skills 目录                  │
│    - Managed (系统级)                    │
│    - User (~/.claude/skills/)           │
│    - Project (.claude/skills/)          │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ 2. 对每个目录执行 fw1() 函数             │
│    - 列出所有子目录                      │
│    - 查找 SKILL.md 文件                  │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ 3. 并行加载所有 skills                   │
│    (Promise.all)                         │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ 4. 合并并去重                            │
│    - 检查 inode 避免重复                 │
│    - 应用优先级规则                      │
└─────────────────────────────────────────┘

2. Skills 加载(Load Phase)

fw1 函数(源代码第 256290 行)负责从目录加载 skills:

async function fw1(A, K) {
    let q = hA(),
        Y = [];
    try {
        let z = q.readdirSync(A);  // 读取目录
        for (let w of z) {
            if (w.isDirectory() || w.isSymbolicLink()) {
                let H = S4A(A, w.name),
                    J = S4A(H, "SKILL.md");  // 查找 SKILL.md
                try {
                    let X = q.readFileSync(J, {encoding: "utf-8"}),
                        {frontmatter: O, content: $} = _$(X);  // 解析 frontmatter

返回的 Skill 对象结构

{
    skill: {
        // 基本信息
        skillName: "_",                    // 目录名称(技术名)
        displayName: O.name,               // frontmatter 中的 name(展示名)
        description: Z,                    // frontmatter 中的 description
        markdownContent: $,                // SKILL.md 的主体内容

        // 权限和工具
        allowedTools: G,                   // allowed-tools 列表
        argumentHint: O["argument-hint"],

        // 配置参数
        whenToUse: O.when_to_use,
        version: O.version,
        model: M,                          // 可指定 LLM 模型
        userInvocable: W,                  // 用户是否可直接调用
        disableModelInvocation: D,         // Claude 是否可自动触发

        // 执行上下文
        source: K,                         // 来源:policySettings/userSettings/projectSettings
        baseDir: H,
        loadedFrom: "skills",
        hooks: j,                          // hook 配置
        executionContext: P,               // fork 或继承
        agent: V                           // 指定的 agent 类型
    },
    filePath: J                            // SKILL.md 文件路径
}

加载关键步骤

  1. 读取目录readdirSync(A)
  2. 查找 SKILL.mdS4A(H, "SKILL.md")
  3. 读取文件readFileSync(J, {encoding: "utf-8"})
  4. 解析 YAML_$(X) 分离 frontmatter 和 content
  5. 构建对象 → 包含所有配置的 skill 对象

3. Frontmatter 解析

SKILL.md 中的 YAML frontmatter 被解析为配置:

---
# 基本信息(必需)
name: analyze-claude-code
description: 分析 Claude Code CLI 源码

# 功能配置(可选)
allowed-tools: [Bash, Read, Grep, Task]          # 限制此 skill 可用的工具
user-invocable: true                             # 允许用户直接调用(/analyze-claude-code)
disable-model-invocation: false                  # Claude 可自动检测并触发此 skill
model: inherit                                   # 继承当前模型,或指定:gpt-4, claude-opus 等
context: inherit                                 # fork 或 inherit(继承当前上下文)

# 文档信息(可选)
when_to_use: |
  当用户想要分析 Claude Code 内部实现时触发
version: 1.0.0
author: Kai Chen

# 高级配置(可选)
agent: general-purpose                           # 使用哪个 agent 执行
argument-hint: "[source_code_path]"              # 参数提示
hooks:                                           # Hook 配置
  - event: SessionStart
    path: hooks/check-version.sh
---

# Markdown 内容从这里开始
[Skill 的执行指令]

解析示例

let {
    frontmatter: O,  // YAML 配置对象
    content: $       // Markdown 主体内容
} = _$(X);           // _$ 是 YAML 解析函数

// 从 frontmatter 提取值
let displayName = O.name;
let description = O.description ?? bg($, "Skill");  // 若无 name,从 Markdown 提取
let allowedTools = wR(O["allowed-tools"]);
let userInvocable = O["user-invocable"] === void 0 ? true : Tw1(O["user-invocable"]);
let disableModelInv = Tw1(O["disable-model-invocation"]);

4. 重复处理(Deduplication)

如果不同位置有相同的 skill,通过 inode 判断是否为同一文件(源代码第 256546 行):

let O = new Map,
    $ = [];

for (let { skill: Z, filePath: G } of X) {
    let W = C9Y(G);  // 获取文件的 inode

    if (W === null) {
        $.push(Z);
        continue;
    }

    let D = O.get(W);
    if (D !== void 0) {
        // 已加载过此 inode,跳过
        u(`Skipping duplicate skill '${Z.name}' from ${Z.source} (same inode already loaded from ${D})`);
        continue;
    }

    O.set(W, Z.source);
    $.push(Z);
}

重复检测的含义

  • 同一文件的符号链接 → 检测到重复,使用已加载版本
  • 不同位置的副本 → 根据优先级,较低优先级的被跳过
  • 示例
    • ~/.claude/skills/my-skill/SKILL.md (inode: 123456)
    • .claude/skills/my-skill/SKILL.md (inode: 123456 - 符号链接)
    • → 只加载一次,使用更高优先级的版本

5. Skills 激活(Activation)

当用户输入触发某个 skill 时的激活流程:

激活条件

✅ 自动激活(Model Invocation)

if (!D) {  // disable-model-invocation === false
    // Claude 可自动检测用户输入与 skill 的 description/when_to_use 的关联
    // 自动加载并注入 skill 内容到系统提示
}

条件:disable-model-invocation: false 或未指定(默认 false)

🖱️ 手动激活(User Invocation)

if (W) {   // user-invocable === true
    // 用户可通过 /skill-name 直接调用
}

命令格式:/analyze-claude-code/plugin-name:skill-name

内容注入

当 skill 被激活时,markdownContent 被注入到当前消息中(源代码第 326808 行):

{
    skillContent: z,  // = skill.markdownContent
    modifiedGetAppState: H,
    baseAgent: O,
    promptMessages: [
        J6({ content: z })  // 内容作为消息内容
    ]
}

执行上下文

// 根据 executionContext 选择执行方式
let P = O.context === "fork" ? "fork" : void 0;

if (P === "fork") {
    // 独立的 agent 执行,上下文隔离
    // 有独立的:
    // - getAppState(获取应用状态)
    // - readFileState(文件读取状态)
    // - toolPermissionContext(工具权限)
} else {
    // 在当前 agent 中执行,继承所有上下文
}

完整激活流程

用户输入
    ↓
┌─────────────────────────────────────┐
│ 1. 匹配 Skill                         │
│    - 手动:/skill-name                │
│    - 自动:匹配 description/when_to_use
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 2. 检查权限                           │
│    - allowed-tools                   │
│    - model(若有指定)                │
│    - hooks(触发前置 hook)           │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 3. 注入内容                           │
│    - 将 markdownContent 加入消息       │
│    - 设置 agent(若指定)             │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 4. 执行 Skill                         │
│    - fork:独立上下文                 │
│    - inherit:共享上下文              │
└─────────────────────────────────────┘
    ↓
完成

6. 优先级和覆盖规则

优先级顺序

优先级 来源 路径 说明
1️⃣ Highest Managed /Library/Application Support/ClaudeCode/skills/ IT 部门或系统管理员设置,强制适用
2️⃣ Medium User ~/.claude/skills/ 用户全局 skills
3️⃣ Lowest Project .claude/skills/ 项目级 skills,同名会覆盖前两级

覆盖规则示例

情景 1:三个位置都有 my-skill/SKILL.md
├─ /Library/Application Support/ClaudeCode/skills/my-skill/ (优先级最高)
├─ ~/.claude/skills/my-skill/
└─ .claude/skills/my-skill/

结果:使用 /Library/Application Support 版本,其他两个被跳过
情景 2:项目和用户级都有 my-skill,但 inode 相同(符号链接)
├─ ~/.claude/skills/my-skill/ (inode: 123456)
└─ .claude/skills/my-skill/ → 符号链接到 ~/.claude/skills/my-skill/ (inode: 123456)

结果:只加载一次,inode 重复检测会跳过符号链接版本

7. 实战示例

你的 analyze-claude-code Skill 加载流程

1. Claude Code 启动
   ↓
2. 扫描 ~/.claude/skills/ 和 .claude/skills/
   ↓
3. 发现 plugins/analyze-claude-code/skills/analyze-claude-code/SKILL.md
   ↓
4. 读取并解析 frontmatter:
   {
     name: "analyze-claude-code",
     description: "Analyze Claude Code CLI source code...",
     user-invocable: true,
     disable-model-invocation: false  // ← Claude 可自动触发
   }
   ↓
5. 将 SKILL.md 的完整内容加载到内存
   ↓
6. 当用户提到 Claude Code 内部实现时:
   a. Claude 自动检测到匹配 skill 的 description
   b. 激活 analyze-claude-code
   c. 将 SKILL.md 内容注入到系统提示
   d. 现在 Claude 可以:
      - 使用 Bash、Read、Grep、Task 工具
      - 参考 SKILL.md 中的指导(搜索模式、代码位置等)
      - 理解如何提取提示、工具定义等
   ↓
7. 如果你添加了 WebFetch 指导:
   Claude 可以在分析时使用 WebFetch 访问官方文档
   例如:https://code.claude.com/docs/llms.txt

添加的 Official Documentation 部分如何工作

## Official Documentation

**Access official docs via WebFetch tool:**
...
- URL: `https://code.claude.com/docs/llms.txt`

加载后:

1. SKILL.md 的这一部分被加载到 markdownContent
   ↓
2. 当 skill 激活时,整个 markdownContent(包括此部分)被注入
   ↓
3. Claude 现在在系统提示中看到:
   "Use the WebFetch tool to query Claude Code's official documentation..."
   ↓
4. Claude 可以主动使用 WebFetch 工具来查询官方文档
   ↓
5. 这改进了分析质量,因为 Claude 可以交叉参考源代码和官方文档

8. 关键代码位置

源代码行号参考

功能 行号 函数/操作 说明
Skills 路径扫描 256530 fw1() 调用 扫描三个 skills 目录
Skills 加载 256290 async function fw1(A, K) 从目录加载单个 skill
Frontmatter 解析 256309 _$(X) YAML frontmatter 解析
Skill 对象构建 256318-256338 G37({...}) 构建 skill 配置对象
重复检测 256546 inode 比对 去重逻辑
内容注入 326808 skillContent: z 将 skill 内容加入消息

搜索模式

# 在 Claude Code 源代码中查找 skills 相关代码
grep -n "SKILL\.md\|fw1\|loadSkill" package/cli.js

# 查找 skill 的应用点
grep -n "skillContent\|markdownContent" package/cli.js

# 查找 skill 的触发条件
grep -n "userInvocable\|disable-model-invocation" package/cli.js

📚 总结

Claude Code 使用渐进式加载策略:

  • 启动时 → 只加载 skill 的元数据(~100 tokens)

    • name
    • description
    • allowed-tools
    • 其他 frontmatter 配置
  • 触发时 → 加载完整内容(<5k tokens)

    • 完整的 Markdown 内容注入到系统提示
    • Claude 可以参考 skill 中的所有指导和示例

这样既保持了启动速度,又在需要时获得完整的上下文。


🔗 相关资源


作者:Kai Chen 日期:2026-01-23 基于:Claude Code CLI 源代码分析

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment