首页 › tutorials › Skill失效诊断与全链路调试实战

概述

本教程聚焦 Claude Code Skill 开发中最令人头疼的环节——Skill 写完却"不工作":要么不触发、要么乱触发、要么触发了却执行出错。我们将建立一套从"发现层 → 触发层 → 执行层 → 验证层"的四层故障定位方法论,掌握 --debug、--verbose、/skills、~/.claude 日志、权限回放等真实调试工具链。适合已能写出第一个 SKILL.md、却卡在"它为什么不按我想的来"的开发者。

前置条件


正文:四层故障定位法

调试 Skill 和调试普通程序最大的区别在于:Skill 的行为由两层逻辑决定——一层是大模型对 description 的语义理解(触发层),另一层是你写在 SKILL.md 正文与脚本里的确定性逻辑(执行层)。当一个 Skill 失效,你必须先判断故障在哪一层,否则就是在黑暗中乱改。下面这套四层法,把"猜测"变成"测量"。

步骤 1:归类失效——先回答"坏在哪一层"

动手之前,先给问题贴标签。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 正文中的特定步骤或术语——有,说明被加载了,是执行层问题;没有,才是触发层问题。

步骤 2:发现层诊断——Claude 到底"看没看见"你的 Skill

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

步骤 3:触发层诊断——"看见了,为何不用我"

如果 Skill 在 /skills 里,却不被调用,问题在语义匹配:Claude 觉得你的 description 跟当前任务"不够相关"。这是 Skill 调试中最微妙的一层。

操作 1:开 --debug 看模型在想什么

最直接的诊断手段是带调试标志启动:

claude --debug

或针对单次提示:

claude -p "帮我把这个 CSV 转成 PDF 表单" --debug --verbose

--debug 会输出工具调用、Skill 加载、上下文裁剪等内部信息;--verbose 会显示更详细的推理过程。在输出里搜索你的 Skill 名,能看到它是否进入了"候选集"却最终落选。

操作 2:审视 description 的三个失败模式

触发层的病根 90% 在 description。对照下面三种失败模式自查:

  1. 过窄(描述太具体,模型想不到用它)
    坏例子: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.

  2. 过宽(描述太泛,到处误触发)—— 即形态 B
    坏例子:description: Helps with file operations.
    这会让它在每个涉及文件的任务里都跳出来。
    改进:明确边界 + 反向约束,例如 Use when ... Do NOT use for general file reading or editing; only for structured PDF form completion.

  3. 缺触发词(用户实际说法与描述词汇不重合)
    收集用户真实说法("填表"、"生成表格"、"打印到 PDF"),把这些动词/名词织进 description。

操作 3:盲测验证

改完 description 后,用一批你不告诉 Claude 用哪个 Skill 的提示词测试。这正是 Evals 盲测的思路——构造 10-20 条真实用户问法,统计触发命中率与误触发率,迭代到稳定。本教程实战环节会给出一套最小可用的盲测脚本。

常见问题:description 越长越好?否。过长的描述会挤占索引上下文,反而稀释关键词。官方建议保持 1-3 句、关键词前置。

步骤 4:执行层诊断——"触发对了,却做错了"

这一层分两类:模型没遵循正文(形态 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: $?"

如果脚本单独跑就崩,那跟模型无关,先修脚本。常见确定性错误:

操作 4:用 --debug 回放权限决策

当 Skill 被权限系统拦截时,--debug 会打印每次工具调用前的权限判定链路,能看清是哪条规则(allow/deny/ask)命中了你:

claude -p "填表" --debug 2>&1 | grep -A3 "permission\|allow\|deny"

步骤 5:修复与回归——让 bug 不再回来

诊断完成后,最后一个习惯决定 Skill 质量:把这次的失效案例固化成回归测试。

  1. 把触发该 bug 的提示词存进测试集。
  2. 在 description 旁补一句 # regression: <bug 简述>,方便日后审计。
  3. 对确定性脚本错误,给脚本本身加单测(pytest),在 SKILL.md 的 ## Verification 里写明如何跑测试。
  4. 重新跑步骤 3 的盲测集,确认旧 bug 修了、且没引入新回归。

调试心法:四层法的精神是"二分定位"。先判断在不在索引(发现层),再判断匹不匹配(触发层),再判断模型读没读懂(执行层-语义),最后才是看脚本对不对(执行层-确定性)。每一层只改一个变量,改完重测,不要一次动 description 又动脚本——否则修好了也不知道是哪个起的作用。


实战案例:拯救一个"装死"的 Skill

我们从零造一个故意写坏的 Skill,然后用四层法把它救活。这个 Skill 叫 csv-to-pdf-table,目标是"把 CSV 转成 PDF 表格"。

第 0 步:造出"坏 Skill"

目录与 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.

第 1 步:归类——它属于哪一层失效?

claude -p "把这个 sales.csv 转成 PDF 表格"

观察:Claude 直接尝试自己用 Python 写代码,完全没碰这个 Skill。→ 形态 A(完全不触发)。进入发现层诊断。

第 2 步:发现层——它进索引了吗?

/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——这次它出现了。

第 3 步:触发层——现在它会触发吗?

claude -p "把 sales.csv 转成 PDF 表格" --debug

在 debug 输出里搜索 csv-to-pdf-table,能看到它进入候选并最终被选中。✅ 触发层通过。

第 4 步:执行层——跑起来了吗?

被触发后,Claude 调用了 convert.py,但报错。读日志:

grep -n "csv-to-pdf-table\|convert.py\|error" ~/.claude/*.jsonl | tail -20

看到两类错误:

  1. ModuleNotFoundError: No module named 'fpdf' —— 依赖缺失
  2. 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.

第 5 步:回归盲测

准备 12 条提示词(6 条应触发、6 条不应触发),统计:

指标 修复前 修复后
命中率(应触发) 0/6 6/6
误触发率 — 0/6
执行成功率 — 6/6

全绿,Skill 救活。


Mermaid 流程图:四层故障定位决策树

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

延伸阅读


训练营适用性评估


参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方 Skill 体系文档,涵盖 frontmatter 规范、渐进披露、作用域与内置 Skill
  2. Skill authoring best practices — Claude API Docs — name/description 字段的官方约束与高质量范例
  3. Debugging Claude Code: Techniques for Skill Development — AI Skill Market — Skill 开发的系统化调试技术,含日志策略与常见错误模式
  4. Debugging and Diagnostics — claude-code-best-practice (DeepWiki) — ~/.claude 日志结构、调试工具与诊断流程
  5. Claude Code Skills: A Practical 2026 Guide — Nimbalyst — Skill vs MCP vs Subagent 决策框架与配置模式
  6. Claude Code 2.1 Cheatsheet (2026) — Awesome 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 等场景全覆盖。

51学通信知识星球海报-扫码加入