一、同一个 Skill,我只改了 description先讲一件事。
有两个 Skill,功能完全一样,正文一个字都没改。差别只在其中那个换了一种 description 的写法。换完之后,两者的调用率差了好几倍。

这不是玄学,也不是运气好碰上了几次合适的提问。这是机制。
上一篇我们划完了四个扩展点的边界,从今天开始一个一个动手。第一个要讲的,就是最反直觉的这一环:你写好的 Skill,Claude 从来不用它。
绝大多数时候,原因不是它写得不好。原因是它的 description 写得像一份文档标题,而不像一句触发条件。
这是有依据的。官方文档在给 Skill 写法的第一条建议里就把这件事说清楚了:description 要说明这个 Skill 做什么,以及什么时候用它,因为 Claude 就是靠它来决定什么时候应用这个 Skill。
注意后半句。是 Claude 靠它来判断。
这句话一出来,description 这个位置的性质就变了。它不是写给人看的说明,它是写给模型看的判断依据。写给人看和写给模型看,是两种完全不同的写法。
写给人看,你可以写「本 Skill 用于规范 Python 代码风格」。这一句写在你团队的 wiki 上,所有人都懂。
写给模型看,你得写「当你准备新增或修改 Python 文件时用它」。这一句才能在某个具体的时刻被匹配上。
差别在哪?第一句描述的是一个东西,第二句描述的是一个时机。
还有一个前提要交代。如果你把 description 整个省掉,Claude Code 会退而取正文里的第一行非空内容当作描述。那就等于把这个位置交给了一个随机句子,通常不会是什么好结果。
今天这一篇,把触发这件事从头拆一遍:模型到底看到了什么,它在哪个环节被截断,为什么你那种看起来写得好好的描述必然失效,以及最后一件事——怎么判断一个 Skill 到底有没有被触发。
二、Claude 决定用不用它的时候,眼前摆着什么先说机制。不搞懂机制,后面所有的写法定式都只能靠硬背。
Claude Code 在会话里会加载一份 Skill 清单进上下文。清单里装的是每个 Skill 的名字和描述。
为什么要这么设计?因为要控制上下文成本。

一份完整的 SKILL.md 可能有几千个字。你要是装了二十个 Skill,把它们全塞进每一轮对话,你的窗口还没开始干活就先满了。所以官方做了分层:清单常驻,正文按需。
官方原话是这么说的:
在一次普通会话里,Skill 的描述会被加载进上下文,这样 Claude 知道有哪些能力可用,但完整的 Skill 内容只有在被调用的时候才会加载。
这句话值得读两遍。
它的意思是:当 Claude 在决定要不要用你那个 Skill 的时候,它眼前摆着的只有清单里的那点东西——名字、description,加上可选的 when_to_use。它看不到 SKILL.md 的正文。
正文是它决定要用之后、也就是买票入场之后,才作为一条消息展开进来的。
所以你写的正文里那句最关键的约束、那个最好用的词,在触发这一刻,全都是隐形的。
这就像你开了一家店。顾客站在门口,只能看到招牌。招牌上没写的东西,里面装修得再豪华也拉不来人。
如果你对这一切还没有体感,我把清单大致的样子写出来给你看。你装了三个 Skill,模型在每一轮对话里看到的东西,大概就是这么三行:
python-style | 本仓库的 Python 代码约定:命名、类型标注、错误处理的分层方式
deploy | 把应用发布到生产环境
fix-issue | 修复指定的 GitHub issue就这些。没有前言,没有目录,没有你精心写的那段"使用本 Skill 前请先确认三点"。
你写的时候是一条一条交代的,它看的时候是一行一行扫过去的。中间这个落差,就是触发率的所有秘密。
第一层:清单
第二层:正文
装着什么
name
+
description(+
when_to_use)
SKILL.md
的全部内容
什么时候进上下文
会话开始就在
被调用之后才作为一条消息进入
进多少人能看见
每个 Skill 都列
只有被调用的那一个
有什么用
决定要不要用它
决定用完之后怎么做
看第三行。这一层设计的目的,就是让模型知道「有哪些能力可用」。注意是"知道有哪些",不是"知道每个怎么做"。
所以 description 这个位置的性质就是广告位,而不是目录页。
你要在这几十个字里干的事,不是介绍这个 Skill 是什么,而是告诉模型,在什么情况下应该想起它。这两个动作看着接近,写出来的句子完全是两回事。
顺带说一句,这条分层不只是为了省你的钱,它也顺手划定了能力边界——模型能主动想起来的,只有清单里写得出来的那些东西。
三、1536 和 1%:两个会吃掉你描述的数字第二个要点是预算。这里有两个数,每一个都会吃掉你的描述。

第一个数是 1536。
官方规定,description 和 when_to_use 合并起来的文字,在 Skill 清单里会在 1536 个字符处被截断。这么做是为了降低上下文占用。
注意,这是合并计数,不是各算各的。
你 description 已经写了 1400 字,when_to_use 再补 300 字,那后面这 164 个字永远不会出现在模型眼前。你等于白写了。
官方给的写作建议就一句话:把你最关键的用例放在最前面。
这句话不是文风建议,是物理约束。因为它不是「建议你写得简洁一点」,而是「超出这个数的部分,模型永远读不到」。
第二个数是 1%。
清单本身也有一份字符预算,这个预算按模型上下文窗口的 1% 来算。窗口越大能装的描述越多,但总归是有限的。
当你装的 Skill 太多、清单塞不下的时候,Claude Code 会开始裁描述。裁的顺序是:从你调用最少的那些 Skill 开始,让你用得最多的那些保留完整文字。
这两个数放在一起,会带来一个非常实际的后果。
假设你装了三十个 Skill,其中有一些你半年没点过一次。那它们的描述会被优先砍掉。而被砍掉的,往往正好是那些能触发它的关键词。
于是形成一个负反馈:
你越不用它,它越不可能被触发它越不被触发,你就越用不上它还有一个更隐蔽的点:清单里名字是永远保留的,只有描述会被裁。
所以一个被裁掉描述的 Skill,在模型眼里就只剩一个孤零零的名字。那基本等于不存在——模型知道有这么个东西,但完全不知道什么时候该想起它。
数字
是什么
超了会怎样
你该做什么
1536 字符
单个 Skill 的
description
+
when_to_use
后面的字被截断,永不参与判断
关键用例放最前面
1% 上下文窗口
整份清单的字符预算
描述被裁,从调用最少的开始
关掉不用的,别让它占位
好在这些数都是可查可调的。
预算能改,最高能调到多少取决于你的设置。想看清单到底占了多少上下文、谁是大头,用 /doctor。想找出那些从来没被调用过、白占位置的 Skill,用 /skill-doctor。这两个命令我建议你今天就跑一次,大概率能砍掉一批。
但在你动手查之前,先把这两个数记住就够了。
四、文档标题式的描述,为什么会必输现在回答那个核心问题:为什么文档标题式的 description 必然失效。
因为你写的是「这个 Skill 是什么」,而 Claude 要判断的是「现在这个请求该不该用它」。你俩描述的根本不是同一件事。
我举四组真实可抄的对照,你可以直接套。
失效写法
有效写法
代码风格
「Python 代码风格规范」
「当需要新增或修改 Python 文件时使用,确保符合本仓库的命名与类型标注约定」
数据库
「数据库迁移工具」
「当需要修改数据库表结构或新增迁移脚本时使用,包括添加字段、改索引和回滚方案」
发布
「发布流程说明」
「当你需要把这个项目发到生产环境时使用,包含跑测试、构建、推送三个步骤,发布前必须确认」
接口
「API 设计约定」
「当你要在这个仓库里写新的 HTTP 接口时使用,统一路由命名、错误返回格式和参数校验方式」
看出规律了吗。
失效写法全都在回答"它是什么"。有效写法全都在回答"什么时候用它"。
第一列的四个,全是名词短语。它们是一样东西的名字,你可以把它们贴在文件夹上、写进 wiki 的目录里,都合适。但它们没有任何一个词能被"现在"这个时刻匹配上。
第二列的四个,全是条件加动作。「当……时使用」这个结构本身就在告诉模型:这是一个可以被触发的场景,触发它需要眼前出现某些迹象。
为什么这么多人都会写成左边那一列?我大概能猜到过程。
你写一个 Skill 的起点,通常是「我发现我总在重复交代同一件事」。于是你打开文件,第一件想做的事就是给这件事起个名字。名字起好了,description 顺手就把名字的解释填进去。这个过程非常自然,自然到你根本不会停下来想:这个名字将来要在什么时刻被用上。
还有一个心理上的原因:文档标题写起来更"安全"。写成「当……时使用」,等于把触发条件公开承诺出去了,将来不灵你就知道是自己的问题。写成「Python 代码风格规范」,听着专业,也没有人能挑刺。
但模型的匹配不是靠专业感做的。你写得越像一份文档的目录,它越没法把它和一个具体的请求对上。
还有两个细节值得单独说。
第一,触发词要用用户会自然说出口的话,而不是团队内部的黑话。
你写「执行 DDL 变更」,用户在打的是「加个字段」。中间这一段距离,就是永远不会被触发的距离。
这件事的难点在于,你自己写 Skill 的时候,脑子里想的是这个领域的专业表述。但触发发生的那一刻,输入的是你(或者你的同事)顺口打出来的那句话。你得站在那个时刻写描述,而不是站在文档的目录页写描述。
第二,把区分度最高的词放在最前面。
前面说了,超长会被截断。但即使没超长,靠前的词权重也更高。
同一个仓库里可能既有代码风格 Skill,又有发布流程 Skill。你的开头那几个字,就是它们之间唯一的分界线。如果你两个 Skill 都以「本项目」开头,那模型在前二十个字里根本分不出该用哪个。
五、when_to_use:给"什么时候用"单开一个位置如果你已经认同了「描述的时候用,不是描述是什么」,那接下来这个问题很自然:我的 description 既想说清楚它做什么,又想说清楚什么时候用,一句话塞不下怎么办。

官方给了一个专门的字段:when_to_use。
它是 description 的补充位,专门放什么时候该调用这个 Skill。官方建议的写法是放触发短语,或者典型的请求例子。
它会被拼接在 description 后面,和 description 一起占用那 1536 个字符的额度。
所以分工很清楚:
description 负责说清这个 Skill 做什么when_to_use 负责把什么时候用写细、写具体什么时候该用它?当你的 description 已经用来讲怎么做事了,或者这个 Skill 的触发场景有好几种、一句话说不完的时候,就把场景挪进 when_to_use。
---
name: python-style
description: 本仓库的 Python 代码约定:命名、类型标注、错误处理的分层方式
when_to_use: 当需要新增或修改 Python 文件时使用;当用户提到「加个函数」「改这个模块」「补类型标注」时使用
---你看这个例子里两个字段的分工。description 在说清楚这份约定包含什么,when_to_use 在列具体的触发场景,甚至把用户可能顺口说出来的那几句话都写进去了。这就是广告位的正确用法。
第二个工具是参数,它把"什么时候用"细化成"用什么参数用"。
argument-hint:你敲斜杠命令时,自动补全里显示的参数提示。比如方括号里的 issue 编号,或者文件名加上目标格式。arguments:把位置参数取上名字,让你在正文里用带名字的占位符引用它们。---
name: fix-issue
description: 修复指定的 GitHub issue
argument-hint: [issue-number]
arguments: issue
disable-model-invocation: true
---
按本仓库的规范修复 GitHub issue $issue。正文里还有几个现成的写法:整体的参数文本、按下标取的参数,以及一个更短的简写。这意味着你可以写一个「修第几号 issue」的 Skill,而不是写一个笼统的「修 issue」。
触发条件越具体,模型判断得越准,你自己用起来也越省事。这两件事做好之后,一个 Skill 才算是真正进入了可被调用的状态。剩下的才是正文写得好不好。
六、三个开关:什么时候该关掉模型触发上一篇讲了三个开关的边界,这里只讲取舍。
写法
你能触发
Claude 能触发
description 进不进上下文
默认
能
能
进
disable-model-invocation: true
能
不能
不进
user-invocable: false
不能
能
进
disable-model-invocation: true 应该给谁用?
官方给的场景很明确:用在那些有副作用、或者你想自己控制时机的流程上,比如提交、部署、发消息。
理由很实在——你不希望 Claude 看你代码写得差不多了,就自作主张地给你部署一遍。
而且这个开关比你想的更彻底。Claude 如果还是试着去调用,Claude Code 会直接拦下这次调用,并且明确指示它不要换个方式把步骤重现一遍。正常结果就是它会建议你自己去敲那条命令。
还有两个附带的后果,知道一下:它同时会阻止这个 Skill 被预加载进 subagent,也会阻止它在定时任务里作为提示词被触发。
user-invocable: false 应该给谁用?
反过来,只有 Claude 能触发,你敲不动它。官方给的场景是那种纯粹当背景知识用的 Skill——比如解释一套老系统怎么运转。Claude 该在相关的时候知道它,但让你手打一条命令去调用一份背景说明,这件事本身就没有意义。
取舍其实只有一条。
问自己:这是一套你要亲手点的步骤,还是一份该被模型自己想起来的知识?
是步骤,就关掉模型触发。是知识,就别关。
这句话听起来像废话,但实际写 Skill 的时候,很多人是把流程型的 Skill 开着模型触发,然后抱怨 Claude 有时候会自己乱跑;把参考型的 Skill 关掉了模型触发,然后抱怨 Claude 从来不用它。这两个抱怨的根因是同一个:你没分清自己写的是哪一种。
七、它到底触发了没有最后一件事:怎么判断一个 Skill 有没有被触发。
这件事最容易被跳过,但它其实是整个流程里最该先做的一步。因为你写完之后不确认,后面所有的优化都是在赌。
官方给的排查清单有这么几步:
检查 description 里有没有用户会自然说出口的关键词确认它出现在「有哪些 Skill 可用」这个问题的答案里换个说法,让你的请求更贴近描述里的措辞如果它是你能手动调用的,直接敲斜杠命令调用它第二条是关键。如果它压根没出现在清单里,那问题就不在触发,而在加载——它可能压根没被发现。这两个是完全不同的问题,别混在一起查。
一个特别隐蔽的坑:YAML 写坏了。
如果你 frontmatter 里的 YAML 语法出错,Claude Code 会把正文加载进来,但元数据是空的。
这时候会发生一件很迷惑的事:你手敲斜杠命令照样能用,Claude 却根本没法拿你的描述去做匹配。你去问它为什么不用,它也不知道——因为它手里那份描述是空的。
加 --debug 跑一次就能看到解析错误。官方还提供了一个校验命令,可以直接对着 skills 目录跑一遍,把解析不了的 SKILL.md 找出来。
还有一个可能:名字撞车。
如果两个 Skill 重名,跑哪个由它们各自来自哪里决定。企业级和个人的会盖过项目级的,项目级里的会盖过内置命令。你如果从别处抄了一个 Skill 放进项目里,恰好和已有的重名,那被触发的可能压根不是你新写的那个。
排查这种事的办法很简单:换个不冲突的名字,再试一次。
最后,我构造一段改 description 前后的小实录。以下为示例,不是真实数据。
一个校验提交信息的 Skill。
原描述六个字:提交信息规范。
跑了一周,一次都没被触发过。Claude 每次都是自己随手写提交信息,写完还问我"这样可以吗"。
改成:当你准备执行提交时使用,按本仓库的格式生成提交信息。
同一类请求里,它开始被稳定调用。
区别在哪?原描述里没有「提交」这个动作的时刻,也没有「提交信息」这个用户真的会打的词。它只是一个分类标签,标签是不会被触发的。
改完之后我又看了一眼清单占用,确认它没有被 1536 那个数裁掉。这一步别省,因为你改长了描述之后,第一次有可能撞上那个上限。
光会触发还不够。
内容一旦多起来,它就会一直留在上下文里,每一轮都在收费。一个触发得刚刚好的 Skill,如果正文写得又臭又长,它会变成一个每轮都在扣你钱的东西。
下一篇讲结构:多大的 Skill 该拆、附属文件怎么摆、什么该写进正文、什么该留在外面。
我自己的 Skill 里,有三个写完从没被触发过,全是描述写成了分类名。你的描述是从文档标题抄的,还是从你会打的那句话里抄的?