<!-- title: 让Skill稳定触发的排查术 -->

# Claude Code Skill 调试实战：让自定义技能稳定触发与行为可控

## 概述

本教程面向已经写过第一个 `SKILL.md`、却卡在"技能死活不触发"或"触发了但行为跑偏"的开发者。你将掌握一套可复用的 Skill 排查方法论：从理解渐进式披露（progressive disclosure）的触发机制，到用 `/skills` 与 `--debug` 建立可观测性，再到重写 description、收敛 allowed-tools 修复行为偏差。教程以一个"函数转测试用例"技能为案例，先植入两类真实缺陷，再一步步诊断修复，全程可复现。

## 前置条件

- 已安装 Claude Code（`claude --version` 能输出版本号）
- 跑通过至少一个自定义 Skill（知道 `~/.claude/skills/<name>/SKILL.md` 的目录结构）
- 一段待测试的真实函数代码（任意语言均可，本教程用 TypeScript）
- 基本的 YAML 与命令行操作能力

---

## 正文

### 第一步：先搞懂 Skill 到底靠什么触发

90% 的"不触发"问题，根因是没理解 Claude Code 的**渐进式披露**机制。它的核心只有一句话：

> 会话启动时，Claude 只把每个 Skill 的 `name` + `description`（即 frontmatter 元数据）加载进上下文；`SKILL.md` 的正文（真正的指令、脚本、模板）**只有在被判定相关时才加载**。

这意味着：

```mermaid
flowchart LR
    A[用户输入] --> B{扫描所有 Skill 的<br/>name + description}
    B -->|匹配度高| C[加载完整 SKILL.md 正文]
    B -->|匹配度低| D[技能被忽略<br/>正文永不加载]
    C --> E[按正文指令执行]
    D --> F[用户以为'坏了']
```

由此推出三条铁律：

1. **description 是唯一的自动触发杠杆**。正文写得再精彩，description 不命中，正文永远不会被读到。
2. **description 要写给"决策者"看**——这个决策者是一个只看你这一两句话、要在几十个技能里做选择的模型。它需要第三人称、动词开头、明确"做什么"+"何时用"。
3. **正文的加载是被动的**。你不能指望靠正文里的关键词来吸引触发。

一个常见误解是"我把关键词堆进 description 就行"。堆砌反而会稀释语义、降低匹配精度。正确做法见第四步。

### 第二步：建立可观测性——给 Skill 装上"仪表盘"

排查的前提是"看得见"。Claude Code 提供三个观测手段，缺一不可。

**手段一：`/skills` 命令确认被发现**

在交互式会话里输入 `/skills`，会列出当前会话可用的所有技能及其状态。第一步永远是确认你的技能是否出现在列表里。

- **没出现** → 是发现（discovery）问题：路径错了、目录名和 `name` 不一致、frontmatter 解析失败。
- **出现了但状态异常** → 比如被标记为禁用，或 `disable-model-invocation: true` 导致它只能显式调用。

**手段二：显式调用做隔离实验**

把"触发判断"这个变量先消掉。在 `/skills` 里手动选中你的技能，或在对话里直接说"请使用 xxx 技能"。如果显式调用能正常工作，说明**正文没问题，问题在 description 触发**；如果显式调用也跑偏，说明**问题在正文逻辑或工具配置**。这一步能帮你把问题域一刀切成两半。

**手段三：`--debug` 查看决策日志**

启动时加上调试标志：

```bash
claude --debug
# 或针对更细的工具调用日志
claude --debug --verbose
```

`--debug` 会打印工具选择、技能加载、上下文注入等内部决策。当技能没触发时，你能在日志里看到模型是否"考虑过"这个技能、为何放弃。这是定位"触发失败"最硬的证据。

> **常见问题**：日志太多看不过来？
> **解决**：用管道过滤关键词，例如 `claude --debug 2>&1 | tee debug.log`，再用编辑器搜索你的技能 `name`，只看相关片段。

### 第三步：构建案例技能（并故意埋两个雷）

我们造一个真实有用的技能：**`fn-to-test`**——给定一个函数，生成对应单元测试。下面是"带病"的 v1 版本。

目录结构：

```
~/.claude/skills/fn-to-test/
└── SKILL.md
```

`SKILL.md` v1（带病版本）：

```markdown
---
name: fn-to-test
description: test helper
allowed-tools: Read, Write, Bash
---

# 函数转测试

你是一个测试专家。请帮用户写测试。

读取用户给的代码，然后生成测试用例，写到一个文件里。
```

这个 v1 看似能跑，实际埋了两个典型缺陷：

- **缺陷 A（触发失败）**：`description: test helper` 太短、太泛、第一人称缺失触发语境。模型看到"test helper"完全无法判断何时该用它——它和"写个测试脚本""帮我测一下"等无数意图竞争，匹配度极低。
- **缺陷 B（行为偏差）**：正文只有"写测试用例，写到文件里"，没有任何质量约束。即便触发成功，它会写出任意风格的测试、写到任意路径，行为不可控。而且 `allowed-tools: Read, Write, Bash` 范围过宽，等于没设防。

接下来两步分别诊断 A 和 B。

### 第四步：诊断缺陷 A——不触发 / 触发错误的修复

**复现**：新建一个会话，贴一段函数代码，说"帮我补测试"。预期它会自动用 `fn-to-test`，实际——大概率什么都没发生，或用了别的技能。

**用 `/skills` + `--debug` 验证**：

1. `/skills` 里能看到 `fn-to-test` → 发现正常，问题在 description。
2. 显式说"请用 fn-to-test 技能"→ 能工作 → 进一步确认是触发问题。
3. `claude --debug` 日志里搜 `fn-to-test`，会看到模型"未匹配"或根本没把它列为候选。

**修复：重写 description**。好的 description 要满足四个要素：第三人称、动词开头、说清"做什么"、点明"何时触发"。对比：

```yaml
# ❌ v1 病变
description: test helper

# ✅ v2 修复
description: |
  Generates unit tests for a given function. Use when the user provides a
  function (any language) and asks for tests, test cases, or coverage for it.
  Produces table-driven tests with happy-path, edge-case, and error-path
  cases, and writes them next to the source file.
```

关键改进点：

- `"Generates unit tests"` —— 动词开头，动作明确。
- `"Use when the user provides a function and asks for tests"` —— 直接告诉模型触发条件，覆盖"补测试/加测试用例/提高覆盖率/写单测"等多种自然语言变体。
- 列出产出物形态（`table-driven`、三类用例、写回源文件旁），让模型知道"匹配后能得到什么"，提升相关度评分。

**验证修复**：重开会话，贴函数说"帮我补单测"，技能应自动触发。若仍偶尔不触发，检查是否有**更强的竞争技能**抢了匹配——比如项目里另有一个 description 更泛的 `test-writer`。多个技能 description 重叠时，模型会选语义最贴近的那个；解决办法是让每个技能的 description 在"领域"或"语言"上差异化。

```mermaid
flowchart TD
    Q[技能不触发] --> R1{出现在 /skills 列表?}
    R1 -->|否| P1[发现层问题: 路径/目录名/frontmatter]
    R1 -->|是| R2{显式调用能工作?}
    R2 -->|否| P2[正文/工具层问题: 见第五步]
    R2 -->|是| R3{description 是否动词开头+含触发条件?}
    R3 -->|否| P3[重写 description]
    R3 -->|是| P4[检查竞争技能描述重叠]
```

### 第五步：诊断缺陷 B——行为偏差与工具收敛

触发修好后，运行发现新问题：测试写出来了，但风格混乱、路径乱放、甚至误删了源文件里的内容。这是正文约束不足 + 工具权限过宽的复合症。

**修复 B-1：用结构化正文约束行为**。把模糊的"写测试"变成有边界的工程指令：

```markdown
---
name: fn-to-test
description: |
  Generates unit tests for a given function. Use when the user provides a
  function (any language) and asks for tests, test cases, or coverage for it.
  Produces table-driven tests with happy-path, edge-case, and error-path
  cases, and writes them next to the source file.
allowed-tools: Read, Write, Edit
---

# 函数转测试

## 何时使用
当用户提供一段函数并希望为其生成单元测试时。

## 执行步骤
1. 用 Read 读取目标函数源码，识别函数签名、参数、返回值、可观测副作用。
2. 至少设计三类用例：正常路径、边界值（空值/极值）、错误路径（异常/非法输入）。
3. 使用与源文件相同的语言与测试框架（无明确框架时，TS 用 Vitest，Python 用 pytest）。
4. 用 Write 把测试写到源文件同级目录的 `<name>.test.<ext>`。
5. 用 Edit 只追加测试文件，禁止修改原始源码。

## 禁止事项
- 不要修改、重命名、删除用户提供的原始源文件。
- 不要引入未在项目中声明的依赖。
- 测试文件超过 300 行时，先与用户确认拆分。
```

注意正文从"自由发挥"变成了"步骤 + 禁止事项"双约束，行为立刻可控。

**修复 B-2：收敛 allowed-tools**。v1 给了 `Bash`，意味着技能能在执行期间跑任意命令——这对一个"生成测试"的技能既无必要又有风险。v2 收敛到 `Read, Write, Edit`：读源码、写测试、追加测试，恰好够用。

> **重要坑提示**：`allowed-tools` 在 frontmatter 里声明的"限制"并不总是被严格执行。社区已记录一个已知问题（anthropics/claude-code #37683）：声明了 `allowed-tools` 后，模型在技能执行期间仍可能调用未列出的工具。因此**不要把 allowed-tools 当作唯一的安全防线**——它更像"建议而非强制"。真正要做的是：(1) 正文用"禁止事项"明确约束；(2) 对涉及删除/网络的操作，配合 settings.json 的 hooks 做硬性拦截；(3) 在受限环境运行高危技能前，用只读沙箱先验证。

**验证修复 B**：再次运行，确认：测试只写不删、路径正确、用例覆盖三类、无多余依赖。若仍偶发越界，回到正文继续收紧"禁止事项"。

### 第六步（可选）：用评测量化"修好了没"

主观判断"现在好多了"不可靠。标准做法是建一个 eval 集：5~10 条**会触发**的 prompt + 3~5 条**不该触发**的负样本，分别在 v1、v2 上跑，统计触发准确率与行为合规率。v2 应在两者上都显著优于 v1。这套"从祈祷到工程化"的评测方法在专门的 Skill 评测教程中有完整展开，本文不重复，只强调一点：**没有评测，所有修复都是猜**。

---

## 实战案例：完整可运行交付物

把上述 v2 整理成一个可直接落地的技能。三个文件，复制即用。

**文件 1：`~/.claude/skills/fn-to-test/SKILL.md`**（即上文 v2 全文）

**文件 2：一份测试输入样本 `examples/auth.ts`**

```typescript
export function canAccess(user: { role: string; active: boolean }, resource: string): boolean {
  if (!user || !user.active) return false;
  if (resource.startsWith('admin')) return user.role === 'admin';
  return true;
}
```

**文件 3：排查命令清单（保存为 `debug.sh` 备查）**

```bash
#!/usr/bin/env bash
# 1. 确认技能被发现
echo "==> /skills 列表（交互式会话内执行）"

# 2. 开启调试，捕获决策日志
claude --debug 2>&1 | tee debug.log
# 在日志中搜索: fn-to-test ，观察是否被纳入候选

# 3. 隔离实验：显式调用，绕过触发判断
#    在会话中输入: 请使用 fn-to-test 技能，为 examples/auth.ts 生成测试

# 4. 对比 v1/v2：保留两个目录，逐个测同样 prompt
#    ~/.claude/skills/fn-to-test          (当前版本)
#    ~/.claude/skills/_archive/fn-to-test-v1
```

**预期结果**：

- v1：说"帮我补测试"→ 多半无反应；显式调用 → 测试风格随机、路径乱、可能误改源码。
- v2：说"帮我补单测"→ 自动触发；产出 `auth.test.ts`，含正常/边界/错误三类用例，源文件未被动。

---

## Mermaid：Skill 排查决策树（完整版）

```mermaid
flowchart TD
    Start[Skill 表现异常] --> Q1{出现在 /skills?}
    Q1 -->|否| F1[发现层: 检查路径/目录名=name/frontmatter YAML]
    Q1 -->|是| Q2{disable-model-invocation?}
    Q2 -->|是 且想自动触发| F2[改为 false 或接受仅显式调用]
    Q2 -->|否| Q3{显式调用正常?}
    Q3 -->|否| Q4{--debug 显示工具被拒?}
    Q4 -->|是| F3[修正 allowed-tools / 正文工具引用]
    Q4 -->|否| F4[正文逻辑缺陷: 补步骤+禁止事项]
    Q3 -->|是| Q5{自动触发失败?}
    Q5 -->|是| F5[重写 description: 动词+触发条件]
    Q5 -->|否| Q6{行为跑偏?}
    Q6 -->|是| F6[正文约束 + 收敛 allowed-tools + hooks 硬拦截]
    Q6 -->|否| Done[稳定可用: 建 eval 持续监控]
    F1 --> Re[重测]
    F2 --> Re
    F3 --> Re
    F4 --> Re
    F5 --> Re
    F6 --> Re
    Re --> Start
```

---

## 常见问题（FAQ）

**Q1：description 写多长合适？**
官方上限是 1024 字符，但实操中 80~200 字符最佳。过长会稀释语义、挤占"加载所有 description"的预算。把最关键的"做什么 + 何时用"放最前面。

**Q2：我堆了很多关键词，为什么反而不触发？**
关键词堆砌让 description 语义模糊，模型难以判断"现在到底该不该用"。改用一两句通顺的第三人称陈述，明确触发场景，精度反而更高。

**Q3：`/skills` 里技能名是灰的/带禁用标记怎么办？**
检查 frontmatter 里 `disable-model-invocation` 是否被设为 `true`——这会让技能只能显式调用。需要自动触发就删掉这一行或设为 `false`。

**Q4：改了 SKILL.md 为什么没生效？**
Skill 元数据在会话启动时加载。**改完要新开会话**（或按工具提示重载），旧会话仍用旧 description。

**Q5：allowed-tools 设了 Read 却还想让它跑命令？**
要么在 allowed-tools 加 `Bash(npm test:*)` 这类带限制的形式（如支持），要么在正文用"禁止事项"约束，并把高危操作交给 hooks 硬拦截——因为 allowed-tools 并非总是被强制执行（见第五步坑提示）。

---

## 延伸阅读

- **Skill 评测基准：从祈祷到工程化** —— 第六步提到的量化方法完整版，教你建正负样本集、跑方差分析。
- **触发优先：从零造精准 Skill** —— 专注 description 触发词工程的进阶篇，与本教程第四步互补。
- **渐进式披露构建分层 Skill** —— 当正文过长时如何用 references/scripts 分层加载，避免一次性灌爆上下文。
- 官方 Skills 文档：https://code.claude.com/docs/en/skills

---

## 训练营适用性评估

- **适合训练营类型**：Claude Code 进阶开发者训练营、AI 工程化实战营、企业内部 Agent 落地工作坊。要求学员已具备"会造 Skill"的基础，本教程解决"造出来不好用"的下一公里问题。
- **预计教学时长**：理论讲授 45 分钟 + 动手实验 90 分钟（含案例复现与缺陷修复）+ 评测环节 45 分钟，合计约 3 小时半。可作为"Skill 开发"模块的收尾实战课。
- **教学建议**：让学员先各自造一个"带病 Skill"互相交换排查，比直接用本文案例更能训练诊断直觉；最后用第六步的 eval 做结业验收。

---

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skills 指南，定义 frontmatter 字段、发现路径与触发机制，是渐进式披露机制的权威出处。
2. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — 逐字段解析 name/description/allowed-tools/disable-model-invocation 等 frontmatter 选项，含示例与注意事项。
3. [Skill authoring best practices — Claude Platform Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — 官方 description 编写最佳实践，第三人称、动词开头、触发条件等写作规范的来源。
4. [allowed-tools in SKILL.md frontmatter does not restrict tool — GitHub Issue #37683](https://github.com/anthropics/claude-code/issues/37683) — 第五步"坑提示"的实证依据，记录 allowed-tools 未被严格执行的已知行为。
5. [Build a Claude Skill: YAML Frontmatter, SKILL.md Instructions](https://sjramblings.io/building-skills-for-claude-part-2/) — 实操教程，补充 frontmatter 格式与正文指令编写的工程经验。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
