如何写 Skill 第6课,分诊五连问:skill 看病指南

0 阅读21分钟

摘要:skill 出了问题没有报错可看:失败是静默的。本文给一棵分诊树,五个问题按序问、问中即停:在吗(装载)、响吗(触发)、照做吗(正文)、跑得动吗(脚本)、灵吗(接口),每层只认自己的证据渠道:菜单、批测、四问全文、stderr 退出码、支持矩阵。附一条 30 秒机械体检命令,和把分诊台写成你自己 skill 的四件骨架。

1、开场:skill 的病没有报错

  我原以为调 skill 能沾调代码的光,结果三样依仗一样都没沾上:报错、断点、复现,skill 一样都不给。想明白这事是在一个下午,我把一个技能的 description 改到 1100 字符,它从斜杠菜单里整个消失了,没有报错,没有堆栈,日志干净得像什么都没发生过。

  这三样在代码世界里怎么干活,你我都熟。报错,堆栈把现场指到你脸上;断点,让世界停在出事那一刻随你看;复现,按同一个铃再犯一次。

  到了 skill 这里全换了一套脾气:失败是静默的,frontmatter 空着能装、不认识的键吞下、不支持的特性装作支持,全都不吭声;运行时在模型脑子里,你没法在别人脑子里下断点;触发还看措辞缘分,上午触发下午沉默,一次根本不算复现。

01-依仗调代码调skill报

  这张表是这一路课攒下来的病历:第 2 课那个 frontmatter 空了照样装载的技能、第 5 课三个特性全被静默忽略的接口,加上开头这个 1100 字符消失案,没有一处出过声。病都焖在锅里。

  我在批改里也撞见过一回。有份作业问"技能写了特性没反应怎么办",答案是连着的三个:"描述没写好""再改改描述""换个措辞试试"。方向不算错,但三个全是病因猜测,没有一个提到层,也没有一个能算证据动作。这不怪写作业的人,病没有报告,人的第一反应自然是猜病因。

  本机装着一本 61 行的官方调试手册,diagnosing-skills,开篇第一句就把最容易搅在一起的两件事分开:装载成功,和会触发,是两码事。分诊树就是把这类分岔一路劈到底:五问,按序,问中即停。往下五节一节一问,第 8 节给总表和一条现在就能跑的命令。

2、分诊台:五连问与证据渠道

  先把分诊台的全貌摆出来。五个问题,前一个有了答案,后一个就别再纠缠:

  1. 在吗:生死病。菜单和目录里找它。
  2. 响吗:缘分病。批测说话。
  3. 照做吗:质量病。四问跑全文。
  4. 跑得动吗:事故病。盯 stderr 和退出码。
  5. 灵吗:白写病。对照支持矩阵(第 7 节见)。

  关键在渠道互不通用。装载层的证据在菜单和目录里,触发层的证据在批测里。你拿着"改描述"去修跳步骤的病,或者在目录和禁用开关之间反复翻找想确认触发,都是白跑。我自己第一回排查"不触发",就在目录里翻来翻去折腾了半天,后来才想起菜单里它明明在,该走的是批测。

  树上有几条负面指令,帮你及时止损:菜单里看得见它,装载层整段跳过;显式选中了它、它也执行了,就别赖触发层,问题在正文;frontmatter 残缺被丢是装载层的死法,未知键被无视是接口层的常态,两个"没反应"住在两层,别串门。

02-否否否否否证据渠道菜单·

  实战起手式是先机械后人工:先跑一遍七查脚本,30 秒把底盘扫完(frontmatter、预算、行数、可达性、接线,加上今天新添的接口方言扫和预载对票两查),再按树往下人肉走。命令在第 8 节给全。

  这套问法不是我发明的。Pocock 的 diagnosing-bugs 里有一句话我很认同:

Build the right feedback loop, and the bug is 90% fixed.

  反馈回路立起来,bug 就修好九成了。搬到 skill 上,回路有三条:

  1. 七查是秒级红绿的体检
  2. 批测管触发
  3. 差分测试管脚本改动。

  动手前再守两条纪律:先列 3 到 5 个可证伪假设再动手;一次只换一个变量,换措辞就是换变量。搬不过来的也有:断点和插桩没有对应物,你没法在模型脑子里下断点,只能改输入看输出。顺带一句分工:Pocock 那套管破案,他给自己的定位是一次只治一个具名故障,不做审计;七查管体检,常备常跑,别互相替班。社区里还有两种打架的说法:一边号称只重写 description 就能让激活成倍改善(出自一篇我没核到原文的测试文),一边说清单超预算被挤掉时改描述毫无用处。两边都没错,病不在同一层,不按序问,你就会在两层之间来回打转。

3、在吗?装载层:先确认它上了桌

  有学员问过我:"什么情况下,斜杠引用不出来?"这是被问得最多的一句,问的人往往已经把目录点开看过三遍了。

3.1 斜杠里找不到它:三个桶

  菜单里没有它,病因落在三个桶里。一,没被发现:放错根目录、藏在点目录里、文件名大小写不对、被配置禁用、插件没启用、总开关没开。二,装载失败被丢:frontmatter 存在,但缺 name 或 description 这类必需字段,整个文件被丢掉。三,同名遮蔽:它其实装进来了,但你看到的是另一个同名者。前两个桶是生死,第三个桶是替身。

3.2 写坏的处死,没写的宽容

  生死两格之间藏着一对反直觉行为。frontmatter 一个字都不写,技能照样装载,name 回退成目录名,菜单里看得见,只是永远不会自动触发;frontmatter 写了但残缺(能解析,缺 name 或 description 这类必需字段),反而直接被丢,本机手册原文是 "Fails to load"。

  丑话说在前头:官方文档对另一种坏法给的说法不同,YAML 解析失败时技能仍会装载,只是字段全空。两种坏法两种下场,出处各管各的,本机口径我信手册,官方口径留一句待真机复验。这个组合的脾气是:它惩罚写坏的,不惩罚没写的。

  顺带排掉一个误会。有人把 ~/.claude/commands/ 下的 commit.md 当 skill 找,引不出来就以为坏了。它不是 skill,是 commands 族群,斜杠菜单里另立门户,别在错误的科室挂号。

3.3 三个数字,三道关

  描述的长度限制到底谁说了算?我把官方文档和本机手册的数字逐个对过账,发现根本不是一道关,是三道,而且量的不是同一个东西。

03-丢弃关·1024量des

  三个数字的出处先分开摆:1024 出自本机运行时手册 diagnosing-skills;1536 出自官方文档 skills 页;约 250 出自本机手册。还有一句丑话说在前头:约 250 和 1536 两处一手出处对不上,一处说模型实际匹配面约 250,一处说清单按 1536 截断,我到今天也没把它俩完全调和。

  我的处理是按离运行时近的本机口径用,1536 的截断行为留一句待真机复验。文档说有,不等于机器上有。

  两道关量的不是同一个东西,所以必须量两次;1024 又小于 1536,第一量就能把最狠的死法筛出来。第一量 description 单字段,对 1024;第二量 description 加 when_to_use 的拼接,对 1536。走两个算例。

  1. 算例一,数字按 1536 的官方口径算:description 写 900,when_to_use 写 700。第一量,900 过关;第二量,拼接 1600,栽在截断关,尾部 64 个字符白写,而且不报错。
  2. 算例二,description 单字段写 1100。第一量就死,整个技能消失:开场那个消失案,破的就是这一格。更扎心的是 1100 小于 1536:拼接线这条它明明过了,照样活不下去,死在第一量。两个数要是当成同一根尺子,这道题怎么算怎么错。

  我在批改里见过有人在这套题上连闪三次:第一回答"会被截取",重测改口"两道都过",终测再改口"正常过"。根因就一个,把三道关当成了同一根尺子。修法我压成一句机械口令:量两次,判两次,照做就不会错。

3.4 改了没效果?先查遮蔽

  "改了没效果"的第一反应,十有八九是怀疑缓存。都不是。调用凭的是 name,装载时同名按发现顺序只装第一份:显式配置、用户级、工作区、插件,同层里 .zcode 排在 .agents 前面。你在改被遮蔽的那份,正主在别处纹丝不动。

  Java 老同学应该秒懂:classpath 上有两个同名类,JVM 挑先出现的那个加载,你改第二个,改到天荒地老也没用。一回事。

3.5 本机的雷:拆了,没消失

  我这台机器上,.zcode/skills 里的条目全部是指向 .agents/skills 的符号链接,两条发现路径找到的是同一个文件,雷被提前拆了(我先前拿 grep 盘点出的"两批镜像",就是符号链接被重复计了一遍数),改哪份都生效。但这颗雷只是被拆了,没有消失:哪天把链接换成独立拷贝,遮蔽立刻变回真风险。

  我的工坊副本(lab 目录里那份 skill-audit)和已安装副本至今要手动 cp 同步,原因很简单:它不在发现根上,不参与装载。

  装载层整节收拢成一句,够你带走:缺席宽容,残缺处死。

4、响吗?触发层:模型凭什么请它进门

  先把词钉死,这节只认这两个词:描述,指 frontmatter 里的 description 加 when_to_use,就是技能清单上常驻的那几行字;正文,指技能被触发后注入的那份全文快照。我在批改里见过这两个词串着用的,后面全乱。

  有学员把问题问得特别准:"斜杠扫到了,也选择了,但是,对话调不调,是什么个情况?"这一问正好踩在两条路的分岔上。

4.1 两条路,一个按钮

  路 A 是显式调用:你在斜杠菜单里选中它,确定性执行,正文直接注入,模型不用判断。路 B 是自动调用:模型自己决定请不请它进门,凭的只有清单上那几行描述,判断是概率的。按钮只有一个:正文注入。

  账要分开算:清单是入场券,每轮都在场,花的是每一轮的钱;正文是响应,点了才上,花的是调用的钱。清单上那几行字有两道限制:1024 量 description 单字段,1536 量 description 加 when_to_use 的拼接。这套字段按轮付租,写太长,地主直接收房或者砍尾巴。

  我装的技能里有一批设了 disable-model-invocation: true,对模型彻底隐身,只走路 A,grill-me、ask-matt 都在其列。要确定性,这是替你按死路 B 的那道闸。

04-①清单常驻:入场券nam

  流程就两步:

  1. 清单常驻:name、描述进系统提示词,每轮在场,模型据此做领地判断。
  2. 正文注入:模型判定这是它的领地,调用技能,全文快照一次性进入上下文,这才算"响"。

4.2 没有关键字匹配器

  描述里写着用户的原话,为什么还是不触发?本机手册里有一句大实话:

There is no keyword matcher.

  没有关键字匹配器。触发是语义层面的领地判断,发生在打开正文之前,模型只看清单,不翻正文。所以原话在列,不等于必触发。

  两个实证。第一,pr 技能的描述里写着 "writing a PR body",我拿"写 PR 描述"去问(这句"描述"是 PR 的说明文字,不是 frontmatter 那个描述),字面全中,它沉默:那轮批测一共五条查询,五个全新子 agent,同机同日。第二,skill-audit 的盲测里,两组同构查询(一条原话含 description 里的"写得有没有问题",一条同义换了问法),一组触发一组不触发,四跑,同机。判断是概率的,这不是 bug,是机制本身。

4.3 真阴性不是病

  反过来的极端也要认:真阴性不是病。问"帮我重构这段代码",没有任何技能认领这块领地,全员沉默,这是正确行为,别当病修。

  lazyskills.sh 上的排障文把这层意思拧成过一句话,大意是:出现在清单里只等于装载完成,会不会开火是另外一步。我头一回读到才意识到,自己一直把这两件事当成一件。

4.4 批测:唯一算数的证据

  概率判断就得用概率的证据。一次触发、一次沉默,都不算数,那叫 n=1,是噪声。官方推荐的姿势是 on/off 基线:同一批查询,技能开和关各跑一遍,对比留痕。查询集 6 到 10 条起步,再少不叫批测,叫碰运气。

  具体动作:先固定一批用户真实会说的话,每条开一个新会话去问,技能调没调以子 agent 的工具留痕为准;然后把技能停用,同批原样再跑一遍,两相对照。多出来的触发和丢掉的沉默,这才各自有名字。我管这叫给缘分病拍片子,拍一次不够,片子要一组。

  触发层整节收拢:只认清单不认正文,一次不算数。

5、照做吗?正文层:当下看不见的指令等于不存在

  再钉一次词,就这一次:正文,技能被触发后一次性注入的全文快照,模型不会回头重读第二遍。批改里我还见过把"500 行写作规范线"和"5000 token 压缩窗口"当成同一个数的,也有不知道 when_to_use 是什么的。两个数管两件事,一个管你写多长,一个管压缩时谁能活下来。

  正文有病,不拦触发。位置正常,开关正常,描述合规,技能照样被扫到、照样触发,病发作在照做这一层:跳步骤、自由发挥、把该执行的当建议听。

5.1 快照的两种死法

  死法分两种亚种,指纹、病根、处方都不一样。

  亚种 a,在场但不常驻。指令写成了叙述:"我们会先运行检查脚本。"模型读到时点头,用的时候想不起来,因为它没绑定动作时刻。处方是三要素常驻句:时机,MUST 或 NEVER,失败动作。docx 技能里有句现成的范本:

After generating any document, MUST run postcheck.py and fix all ❌ errors.

  时机(生成任何文档之后)、强制词(MUST)、失败动作(修掉所有 ❌),三样齐了。我后来照这句,把自己评审类 skill 里的叙述句一句一句换了个遍。

  亚种 b,物理不在场。正文太长,会话压缩时只保留最前面约 5000 token(官方文档口径),多技能还共享约 25000 token 的总预算,尾部直接失传。指纹很好认:短会话一切正常,长会话开始跳后半段的步骤,新会话恢复之后走样得更厉害。

  处方是前置和分级,不是改措辞:红线句搬进正文开头,超长内容分级自救。pdf 技能正文 1004 行,靠的就是开头的 Triage 分级活下来。500 行是写作规范线,超线不是死刑,超线又没有自救才是。

05-维度亚种a·在场不常驻亚

5.2 四问检查单

  给正文做体检,四问跑全文:常驻性,关键指令绑没绑时机和强制词;简洁性,这句还能不能更短;归属,这条指令该不该住在正文里;行数,超没超 500 行线,超了有没有分级自救。每一问都要对着原文答,别凭印象点头。

5.3 摘录不可信:一次判了 3/5 的实验

  第 3 课做过一个盲判实验,我自己先折了。拿正文摘录去判 5 道题,对 3 错 2,两个坑全踩:一个坑是那条例外条款只写在正文别处,摘录里根本没有;另一个坑是分级自救装置,摘录里同样看不见。最疼的一题,全文审计之后结论精确反转。从那以后我信一条铁律:检查单必须跑全文,摘录只能用来迷路。

  正文层整节收拢:当下看不见的指令等于不存在。

6、跑得动吗?脚本层:只有输出回得了话

  脚本和模型之间,回话通道只有三条:stdout、stderr、退出码。做 Java 的都熟,这就是下游服务给你的响应码、报文和日志,除此之外它什么都不欠你。所以脚本层的病全部按这三条通道分诊:没跑、跑不动、没人听。第 4 课讲过脚本四分法,这节只管排查入口。

6.1 没跑:先查接线

  指纹:模型回复得头头是道,但通篇没有一处脚本输出的痕迹,结果像脑补的。病根多半是接线病:正文压根没点名脚本,模型不知道它存在,自然不会跑。

  修法在正文调用句,四要素一样不能少:绝对编址、解释器回退、输出说明、MUST。抄一个能用的骨架:

生成文档后 MUST 运行 python "${CLAUDE_SKILL_DIR}/scripts/check.py" <目标目录>;
python 不存在则改用 py 重试;输出为 JSON,逐条读取;有 ❌ 必须修复后重跑。

6.2 跑不动:四个环境病

  指纹变了:stderr 里有报错,或者一声不吭直接退。四个环境病挨个换:

  1. 裸相对路径。工作目录不由你定,脚本里的 scripts/check.py 全靠 cwd 赌运气,改成 CLAUDE_SKILL_DIR 变量编址,写法照上面骨架。
  2. 解释器。有的机器上是 python3,有的只有 py,调用句里写好回退。
  3. 跨平台路径。Git Bash 的 /tmp 和 Windows Python 眼里的 /tmp 同名不同义,一跑就分家。现场复现的探针:Git Bash 里 cd /tmp && touch p.txt,再问 Python 一句 python -c "import os;print(os.path.exists('/tmp/p.txt'))",返回 False 就是分家实锤(本机若恰好存在 C:\tmp,直接问 exists('/tmp') 会返回 True 骗你,务必拿具体文件验)。
  4. 执行期权限。脚本没加执行位、往只读目录里写文件,都会在这一层摔。

  docx 技能里有一条现成的官方禁令,就是给第 1 个病立的碑:

NEVER use bare python3 scripts/....

6.3 没人听:输出就是 API

  最冤的一种:脚本跑了,输出也在,行为就是不变。契约病,四种死法:

  1. 交互卡死,脚本里藏了一个 input(),模型永远不会替你敲回车;
  2. 退出码无纪律,错了也返回 0,模型以为大获全胜;
  3. 错误进 stdout,和正常输出混在一起,模型把报错当数据读;
  4. 大结果 print 全文,关键信息淹死在刷屏里。

  修法是把输出当 API 设计:结果落盘,stdout 只给摘要,长输出支持偏移分页。

  最后说个自己的事。audit_skill.py 验证期我自己抓出 3 个 bug,其中一个是启发式误杀 ./ 开头的相对引用。能抓出来靠的不是瞪眼,是差分基准:夹具埋坑(今存两具,在 lab/skill-audit/fixtures/ 下),配干净对照——tdd、code-review 两个公认干净的技能实跑零误报——跑一遍看谁误报谁漏报。脚本自己也要差分测试,修完必端到端跑一遍,不然你永远不知道修好的是脚本还是错觉。

06-脚本层排查卡:先分类,再

  排查卡收拢:

  1. 没跑:模型不提脚本、结果像脑补。第一步翻正文找调用句。修法补四要素。
  2. 跑不动:stderr 报路径或解释器错。第一步看第一行报错。修法四个环境病逐个换。
  3. 没人听:输出在、行为不变。第一步查退出码和输出去向。修法立输出契约。

  脚本层整节收拢:没跑查接线,没人听查契约。

7、灵吗?接口层:写了等于没写

  第 1 节那份"描述没写好"三连,拿到这一节来走流程,第一步不是改描述,是查矩阵。接口层的病不是 bug,是没人接单:特性写了,运行时不认识,静默忽略。官方文档写明,不认识的字段直接无视,不报错。

7.1 先查矩阵,再谈为什么

  frontmatter 的字段有三类读者:description 写给模型,argument-hint 写给人类,剩下的写给运行时。运行时不认就装没看见。所以"特性没反应"的确诊动作只有一个:对照支持矩阵。skill-audit 今天新添的两查干这件事,interface-dialect 把占位符、预载、方言键扫一遍,preload-allowlist 把预载逐条对票(票,就是 allowed-tools 里声明的许可条目)。真跑一遍最直观,靶子是我埋好的差分夹具(lab/skill-audit/fixtures/commit-plugin,把当年那个预载出缺口的 commit 命令包皮成了技能目录),输出是 JSON,摘两条要紧的 detail:

07-$python~.age

  逐条读。第一条,方言扫抓到四条预载,判词是本机不执行、不拦截,字面透传。第二条,对票:四条预载三条无票,git diff、git branch、git log 都不在票面上,非 auto 权限下渲染即中止。人肉审计这几个缺口要翻半天,这两查一秒复现。当年那个插件的实体如今已经从机器上卸载了,数字出自当日实测,夹具固化在 lab/skill-audit/fixtures/ 里,随时重跑。

  第 5 课给过完整的支持矩阵,这里不重讲,只说一个我从矩阵里看出来的梯度:越依赖运行时实现的特性,装机实际使用越少。argument-hint 全机我只找到 5 处在用,fork 是零(2026-10-09 全机盘点,符号链接去重后计数)。

7.2 三个赌注,三个替代

  三个高频特性,本质是三个赌注,赌的是运行时替你实现了它。矩阵上 ❌,就是赌输了。输了的正确动作是换替代,不是反复重试:

  1. 赌填槽(ARGUMENTS 占位符替你接参数):输了改成尾随声明,正文写明"消息末尾跟的参数就是输入",再配空参数时的兜底动作。
  2. 赌预载(!cmd 替你先跑命令):输了就让模型自己查;真需要预载,逐条进 allowed-tools 拿票。
  3. 赌派工(fork 替你起子 agent):输了就在对话里手工派,模型听得懂"起一个子 agent 去做什么"。

7.3 一个中文作者专属坑

  这个坑我自己踩的。预载语法要求 ! 出现在行首或紧随空白(空格算,全角标点不算),而我敲冒号时输入法给了全角,全角冒号后面再跟 !cmd,不算空白,预载静默不识别。没有报错,只有不生效,和本课所有病一个脾气。检查动作一句话:冒号用半角,冒号后留一个半角空格。这个坑七查查不出(不识别就不报,方言扫反而报无预载),只能手动 grep:grep -n ':!' SKILL.md,有输出就是中了。

  接口层整节收拢:赌机制不如写兜底。

8、收拢:一张表,和你自己的分诊台

8.1 一张表收拢

08-问病名一眼症状证据渠道判

  三处易混再钉一遍:菜单里看得见但不触发,别在装载层打转,去批测;显式选中了却不照做,别赖触发层,去查正文;frontmatter 残缺被丢是装载层的死法,未知键被无视是接口层的常态,症状像,层不同。

8.2 跑一次七查

  就是 7.1 亮过的那条命令,你在自己的技能目录上现在就能跑:

python ~/.agents/skills/skill-audit/scripts/audit_skill.py <skill目录>

  七查是机械扫,30 秒出结果,管底盘:frontmatter、预算、行数、可达性、接线,再加接口的方言扫和预载对票两查。但先交代七查的盲区:预算查量的是 1536 拼接线,1024 那道单字段丢弃关它不量:开场那个 1100 字符消失案,七查跑出来全是 pass,得按 3.3 的量两次自己量。扫完记住一条:脚本的 warn 是证据,不是判决。它说 warn,只说明此处值得人看一眼,定罪永远归你。

8.3 把分诊台写成你的 skill

  分诊这件事本身,官方已经示范过怎么写成技能。diagnosing-skills,61 行,和 diagnosing-bugs 的 138 行对照着读,骨架是同样的四件:

  1. 症状表:症状、病因、修法,一行一条,先对号再治病。
  2. 有序流程:带完成判据,diagnosing-skills 的原话是 "stop when the cause is found",找到即停。
  3. 反馈回路优先:diagnosing-bugs 的原话是 "No red-capable command, no Phase 2",没有能变红的命令,就不许进下一阶段。
  4. 只诊不改:官方手册的定位流程到病因为止;修复是另一件事,我自己的 skill-audit 在边界节里写死了"移交 skill-creator",诊的不治,治的不诊。早停管流程在哪一步停,这条管手别伸多长。

  我自己的 skill-audit 就是照这四件写的:流程五步,JSON 读法一节,边界一节。你手头如果也有一摊反复出毛病的技能,照这四件写一个自己的分诊台,比每次凭记忆翻聊天记录强。

  全文收拢就一句:

  先分层,再取证:症状翻译到层,证据各回各的渠道。

带走三样

  1. skill 的病没有报错,只有症状。全部分诊动作是先把症状翻译到层,再让每层只回答自己证据渠道的问题。
  2. 三道关量两次判两次:第一量 description 单字段对 1024,第二量 description 加 when_to_use 拼接对 1536;呈现面按约 250 排触发词。
  3. 触发是概率判断,只认清单不认正文。证据靠 6 到 10 条批测加 on/off 基线,一次不算数。

临走三问

  1. 你改了工作区里某个 skill 的正文,重启后行为纹丝不动,而斜杠菜单里它明明还在。合上文章,凭记忆走:你的第一查落在哪里?
  2. 一个技能的 description 写到 1100 字符,没写 when_to_use,菜单里根本找不到它。它倒在哪一道关?把 description 砍到 900、补上 700 字符的 when_to_use 之后,又会倒在哪儿?
  3. 同一句"帮我写 PR 描述",你随手测了 3 次,只有 1 次触发 pr 技能。此刻该改措辞,还是先干什么?那条门槛是几条查询起步?

  三道都答对了,这一课才算真的带走。

  第一课写下第一个能跑的技能,第二课修触发,第三课磨正文,第四课接脚本,第五课对接口矩阵;这一课把五层拧成一句问诊的顺序。下一课是第 7 课,进模块三·进阶工艺:会分诊只是会看病,开方子的手艺从那儿开始。


共约 6576 字 · 阅读约 16 分钟
如果这篇文章对你有帮助,欢迎转发给需要的同事和朋友。