本教程聚焦 Claude Code Skill 开发中最令人头疼的环节——Skill 写完却"不工作":要么不触发、要么乱触发、要么触发了却执行出错。我们将建立一套从"发现层 → 触发层 → 执行层 → 验证层"的四层故障定位方法论,掌握 --debug、--verbose、/skills、~/.claude 日志、权限回放等真实调试工具链。适合已能写出第一个 SKILL.md、却卡在"它为什么不按我想的来"的开发者。
~/.claude/skills/<name>/SKILL.md 的基本结构)name、description 两个字段)ls、cat、tail、grep 定位文件调试 Skill 和调试普通程序最大的区别在于:Skill 的行为由两层逻辑决定——一层是大模型对 description 的语义理解(触发层),另一层是你写在 SKILL.md 正文与脚本里的确定性逻辑(执行层)。当一个 Skill 失效,你必须先判断故障在哪一层,否则就是在黑暗中乱改。下面这套四层法,把"猜测"变成"测量"。
动手之前,先给问题贴标签。Skill 失效只有四种形态,每种对应不同的诊断路径:
| 失效形态 | 典型现象 | 故障层 | 首选工具 |
|---|---|---|---|
| A. 完全不触发 | 明明该用它,Claude 却自己硬干 | 发现/触发层 | /skills、--debug |
| B. 错误触发 | 不该用时它跳出来,干扰正常对话 | 触发层(描述过宽) | description 审查、盲测 |
| C. 触发了但执行错 | Skill 被加载,但产出不符合 SKILL.md 指令 | 执行层(模型未遵循) | --verbose、日志回放 |
| D. 执行中报错/卡住 | 脚本崩溃、权限被拒、路径找不到 | 执行层(确定性代码) | ~/.claude 日志、--debug |
操作说明:在终端启动一个干净的会话,故意输入一个"本应触发你的 Skill"的提示词,观察 Claude 的反应。
预期结果: - 如果 Claude 毫无反应地自己回答 → 形态 A - 如果它调用了别的 Skill 或在不相干任务里激活 → 形态 B - 如果它说"我来用 xxx skill"但做法完全偏离你的正文 → 形态 C - 如果它报错、停下、或反复重试 → 形态 D
常见问题:很多人把形态 C(模型没读正文)误当成形态 A(不触发),于是一直改 description,越改越糟。判别关键:看 Claude 的回复里有没有出现你 SKILL.md 正文中的特定步骤或术语——有,说明被加载了,是执行层问题;没有,才是触发层问题。
Skill 的触发依赖渐进披露机制:Claude 启动时只会把所有 Skill 的 name + description 加载进上下文(轻量索引),只有当某个 Skill 被判定为相关,才会读取完整 SKILL.md 正文。所以第一步永远是确认你的 Skill 进了索引。
操作 1:用 /skills 命令查看已注册列表
在交互式会话中直接输入:
/skills
它会列出当前会话可用的全部 Skill(含内置与你自定义的)。如果你的 Skill 不在这个列表里,说明它压根没被发现——问题出在文件系统,不在模型。
操作 2:核对目录结构
自定义 Skill 必须放在以下三个作用域之一,且文件名严格为 SKILL.md(大写、不是 skill.md 或 SKILL.MD):
~/.claude/skills/<skill-name>/SKILL.md # 用户级(全局)
<project>/.claude/skills/<skill-name>/SKILL.md # 项目级
<plugin>/skills/<skill-name>/SKILL.md # 插件级
用一条命令快速排查:
ls -la ~/.claude/skills/*/SKILL.md 2>/dev/null
ls -la .claude/skills/*/SKILL.md 2>/dev/null
操作 3:验证 frontmatter 合法性
/skills 列表是 Skill 被"发现"的硬门槛。最常见的"不进列表"原因是 frontmatter 格式错误。官方规范要求:
name:最多 64 字符,必须与目录名一致,仅限 kebab-case(小写字母、数字、连字符)description:必填,自然语言说明"做什么 + 何时用",建议 1-3 句---
name: pdf-form-filler
description: Fills PDF forms from structured data. Use when the user asks to populate, fill, or complete a PDF form with field values.
---
常见问题与解决:
- 目录名与 name 不一致 → 发现失败。改目录名或改 frontmatter,二者必须完全相同。
- YAML 缩进/冒号后缺空格 → 整个 Skill 被静默忽略。用 cat -A SKILL.md | head -5 看是否有隐藏的 Tab 或异常字符。
- name 含大写或下划线 → 如 PDF_Filler,直接被判非法。改成 pdf-form-filler。
如果 Skill 在 /skills 里,却不被调用,问题在语义匹配:Claude 觉得你的 description 跟当前任务"不够相关"。这是 Skill 调试中最微妙的一层。
操作 1:开 --debug 看模型在想什么
最直接的诊断手段是带调试标志启动:
claude --debug
或针对单次提示:
claude -p "帮我把这个 CSV 转成 PDF 表单" --debug --verbose
--debug 会输出工具调用、Skill 加载、上下文裁剪等内部信息;--verbose 会显示更详细的推理过程。在输出里搜索你的 Skill 名,能看到它是否进入了"候选集"却最终落选。
操作 2:审视 description 的三个失败模式
触发层的病根 90% 在 description。对照下面三种失败模式自查:
过窄(描述太具体,模型想不到用它)
坏例子:description: Fills IRS 1040 forms.
用户说"帮我填税务表"时,模型可能不会联想到。
改进:description: Fills PDF forms from structured data. Use when the user wants to populate, fill, or complete any PDF form with field values, including tax forms, applications, and contracts.
过宽(描述太泛,到处误触发)—— 即形态 B
坏例子:description: Helps with file operations.
这会让它在每个涉及文件的任务里都跳出来。
改进:明确边界 + 反向约束,例如 Use when ... Do NOT use for general file reading or editing; only for structured PDF form completion.
缺触发词(用户实际说法与描述词汇不重合)
收集用户真实说法("填表"、"生成表格"、"打印到 PDF"),把这些动词/名词织进 description。
操作 3:盲测验证
改完 description 后,用一批你不告诉 Claude 用哪个 Skill 的提示词测试。这正是 Evals 盲测的思路——构造 10-20 条真实用户问法,统计触发命中率与误触发率,迭代到稳定。本教程实战环节会给出一套最小可用的盲测脚本。
常见问题:description 越长越好?否。过长的描述会挤占索引上下文,反而稀释关键词。官方建议保持 1-3 句、关键词前置。
这一层分两类:模型没遵循正文(形态 C)和脚本/工具确定性报错(形态 D)。诊断手段完全不同。
操作 1:形态 C——用 --verbose 看模型读了什么
形态 C 的典型表现是 Claude 说了"我来用 pdf-form-filler",但实际步骤完全不对。原因往往是:SKILL.md 正文太长/太散,关键步骤被淹没;或正文里有歧义指令。
claude -p "..." --verbose
排查清单:
- 正文是否超过模型"愿意读完"的长度?关键步骤前置,把背景知识下沉到 references/ 子文件(渐进披露)。
- 是否存在矛盾指令?例如正文先说"直接调用脚本",又说"先询问用户",模型无所适从。
- 是否缺少预期输出格式?明确写出"完成后输出 JSON 且包含 status 字段",模型遵循率显著提升。
操作 2:形态 D——读 ~/.claude 日志
形态 D 是确定性错误,必须看实际执行日志。Claude Code 把会话记录写入 ~/.claude/ 下。关键定位命令:
# 查看最近的会话日志文件
ls -lt ~/.claude/ | head
# 在日志中搜索你的 Skill 名与脚本调用
grep -n "pdf-form-filler" ~/.claude/*.jsonl
grep -n "fill_form.py" ~/.claude/*.jsonl
# 看最近的工具调用与报错
grep -n "error\|Error\|denied\|exit" ~/.claude/*.jsonl | tail -30
操作 3:独立复现脚本
Skill 正文里调用的脚本,脱离 Claude 单独跑一遍,是最快的定位手段:
# 假设 Skill 调用了 scripts/fill_form.py
cd ~/.claude/skills/pdf-form-filler
python3 scripts/fill_form.py --input sample.json --output out.pdf
echo "exit code: $?"
如果脚本单独跑就崩,那跟模型无关,先修脚本。常见确定性错误:
/home/me/...,换机器就失效。改用相对 Skill 目录的路径,或在正文里要求 Claude 先 cd 到 Skill 目录。~/.aws/ 或执行 rm,命中权限策略被拦截。日志里会看到 denied。需在 settings.json 的 permissions.allow 里显式放行,或改用沙箱。import 报 ModuleNotFoundError。在 SKILL.md 里加 ## Prerequisites 说明 pip install 步骤,或用 inline 安装。操作 4:用 --debug 回放权限决策
当 Skill 被权限系统拦截时,--debug 会打印每次工具调用前的权限判定链路,能看清是哪条规则(allow/deny/ask)命中了你:
claude -p "填表" --debug 2>&1 | grep -A3 "permission\|allow\|deny"
诊断完成后,最后一个习惯决定 Skill 质量:把这次的失效案例固化成回归测试。
# regression: <bug 简述>,方便日后审计。pytest),在 SKILL.md 的 ## Verification 里写明如何跑测试。调试心法:四层法的精神是"二分定位"。先判断在不在索引(发现层),再判断匹不匹配(触发层),再判断模型读没读懂(执行层-语义),最后才是看脚本对不对(执行层-确定性)。每一层只改一个变量,改完重测,不要一次动 description 又动脚本——否则修好了也不知道是哪个起的作用。
我们从零造一个故意写坏的 Skill,然后用四层法把它救活。这个 Skill 叫 csv-to-pdf-table,目标是"把 CSV 转成 PDF 表格"。
目录与 frontmatter 故意埋了三个雷:
mkdir -p ~/.claude/skills/CSV_to_PDF/scripts
写入 ~/.claude/skills/CSV_to_PDF/scripts/convert.py:
#!/usr/bin/env python3
import csv, sys
from fpdf import FPDF # 故意不装依赖
def convert(csv_path, pdf_path):
with open(csv_path) as f:
rows = list(csv.reader(f))
pdf = FPDF()
pdf.add_page()
for row in rows:
pdf.cell(0, 8, " | ".join(row), ln=True)
pdf.output(pdf_path) # 故意用相对路径写死
if __name__ == "__main__":
convert("data.csv", "out.pdf") # 故意写死文件名,不接受参数
写入 ~/.claude/skills/CSV_to_PDF/SKILL.md(注意目录名/大小写/name 全是坑):
---
name: CSV2PDF
description: helps with files
---
# CSV to PDF
Just run the script.
claude -p "把这个 sales.csv 转成 PDF 表格"
观察:Claude 直接尝试自己用 Python 写代码,完全没碰这个 Skill。→ 形态 A(完全不触发)。进入发现层诊断。
/skills
列表里没有 CSV2PDF 或 csv-to-pdf-table。说明根本没被发现。用排查命令定位:
ls -la ~/.claude/skills/*/SKILL.md
发现三个问题,逐一修:
| 问题 | 诊断 | 修复 |
|---|---|---|
目录名 CSV_to_PDF 含大写下划线 |
不符合 kebab-case 规范 | 改为 csv-to-pdf-table |
frontmatter name: CSV2PDF 与目录名不符 |
发现失败 | 改为 name: csv-to-pdf-table |
description: helps with files |
即便进索引也匹配不上 | 改写(见下) |
修复后的 frontmatter:
---
name: csv-to-pdf-table
description: Converts a CSV file into a formatted PDF table. Use when the user asks to turn CSV data into a PDF, export a spreadsheet to PDF, or print CSV as a table document. Do NOT use for general file conversion or PDF reading.
---
重命名目录、改完 frontmatter 后,新开会话(旧会话的索引已缓存,必须重启)再 /skills——这次它出现了。
claude -p "把 sales.csv 转成 PDF 表格" --debug
在 debug 输出里搜索 csv-to-pdf-table,能看到它进入候选并最终被选中。✅ 触发层通过。
被触发后,Claude 调用了 convert.py,但报错。读日志:
grep -n "csv-to-pdf-table\|convert.py\|error" ~/.claude/*.jsonl | tail -20
看到两类错误:
ModuleNotFoundError: No module named 'fpdf' —— 依赖缺失FileNotFoundError: 'data.csv' —— 脚本写死文件名独立复现确认:
cd ~/.claude/skills/csv-to-pdf-table
python3 scripts/convert.py; echo "exit: $?"
# ModuleNotFoundError
修复脚本——参数化 + 在 SKILL.md 声明依赖:
#!/usr/bin/env python3
import csv, sys, os
from fpdf import FPDF
def convert(csv_path, pdf_path):
if not os.path.exists(csv_path):
sys.exit(f"input not found: {csv_path}")
with open(csv_path) as f:
rows = list(csv.reader(f))
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=8)
for row in rows:
pdf.cell(0, 6, " | ".join(row)[:120], ln=True)
pdf.output(pdf_path)
print(f"wrote {pdf_path}")
if __name__ == "__main__":
convert(sys.argv[1], sys.argv[2])
在 SKILL.md 正文补依赖与调用约定:
## Prerequisites
Run once: `pip install fpdf2`
## Steps
1. cd to this skill directory.
2. Run: `python3 scripts/convert.py <input.csv> <output.pdf>`
3. Report the output path to the user.
准备 12 条提示词(6 条应触发、6 条不应触发),统计:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 命中率(应触发) | 0/6 | 6/6 |
| 误触发率 | — | 0/6 |
| 执行成功率 | — | 6/6 |
全绿,Skill 救活。
flowchart TD
Start([Skill 失效]) --> Q1{在 /skills 列表里吗?}
Q1 -->|否| L1[发现层: 排查目录名/文件名/frontmatter]
L1 --> Fix1[改 kebab-case 目录 + name 一致 + description 合法]
Fix1 --> Restart[重启会话重测]
Restart --> Q1
Q1 -->|是| Q2{被触发了吗?<br/>看 --debug}
Q2 -->|否/乱触发| L2[触发层: 审查 description]
L2 --> Q2a{问题描述过窄/过宽?}
Q2a -->|过窄| Fix2a[扩写触发词与场景]
Q2a -->|过宽| Fix2b[加反向约束 Do NOT use]
Fix2a --> Blind[盲测集回归]
Fix2b --> Blind
Q2 -->|是| Q3{执行符合正文吗?}
Q3 -->|否| L3[执行层-语义: 正文太长/有矛盾/缺输出格式]
L3 --> Fix3[前置关键步骤, 下沉背景到 references/]
Fix3 --> Blind
Q3 -->|是但报错| L4[执行层-确定性: 读 ~/.claude 日志]
L4 --> Solo[脱离 Claude 单独跑脚本复现]
Solo --> Q4{错误类型?}
Q4 -->|路径错误| Fix4a[相对路径化]
Q4 -->|权限被拒| Fix4b[settings.json 放行]
Q4 -->|依赖缺失| Fix4c[补 Prerequisites]
Fix4a --> Verify
Fix4b --> Verify
Fix4c --> Verify
Verify[独立运行 exit 0] --> Done([固化回归测试])
Blind --> Done
Fix3 --> Blind
code.claude.com/docs/en/skills)—— frontmatter 规范、渐进披露机制、作用域权威说明platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices)—— name/description 字段的官方约束与范例/skills、--debug、--verbose 等 CLI 标志速查~/.claude 日志结构与权限回放name/description 字段的官方约束与高质量范例~/.claude 日志结构、调试工具与诊断流程/skills、--debug、--verbose 等 CLI 标志与命令速查[!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 等场景全覆盖。
