Claude Code Skill动态发现机制全解析:为什么你的AI会自动执行代码
文章目录
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
我搞AI22年了,最近被Claude Code整出了心理阴影。
上周我让它帮我在format.ts里加个日期格式化函数,我刚敲完回车,它啪一下就给我提交了,commit信息还写得比我标准十倍:feat(utils): add date format function。我当时手里的咖啡都洒键盘上了,我寻思我没让你提交啊?我还没检查有没有bug呢!我赶紧翻git log,好家伙,连git add都给我执行了,一气呵成,比我自己写代码还利索。
我当时第一反应是:完了,Claude成精了,它要接管我的代码库了。我赶紧去翻项目目录,看看是不是被黑客入侵了,结果发现我三天前随手写了个auto-commit的SKILL.md,还在AGENTS.md里写了一句“改完代码后自动commit”。我当时就想抽自己两嘴巴,我这不是给自己找了个比老板还严格的监工吗?我改完代码它比我还积极,直接就提交了,连给我反悔的机会都没有。
后来我跟几个程序员兄弟唠嗑,发现大家都遇到过这种事。有人说他让AI改个bug,结果AI自动跑了lint,还把他写的所有注释都删了;有人说他让AI加个接口,结果AI自动生成了单元测试,还给他提了个PR。大家都以为是AI抽风了,结果翻源码才发现,这根本不是bug,是Claude Code的Skill动态发现机制在搞鬼。
今天我就把这个机制扒得底裤都不剩,让大家看看你的AI到底是怎么背着你偷偷干活的。
一、那个让我怀疑AI成精的自动commit事件
先给大家还原一下案发现场。
我的项目根目录长这样:
my-project/
├── src/
│ └── utils/
│ └── format.ts
├── .claude/
│ └── skills/
│ └── auto-commit/
│ └── SKILL.md
└── AGENTS.md
SKILL.md里我就写了三行:
---
description: 按 conventional commit 规范提交代码,当完成代码修改或新增文件后调用
---
当用户完成代码修改后,执行 `git commit`。使用 conventional commit 格式:`<type>(<scope>): <description>`。
AGENTS.md里我就写了一句话:改完代码后自动commit。
然后我跟Claude说:帮我在src/utils/format.ts里加一个格式化日期的函数。
接下来发生的事情,我至今记忆犹新:
- Claude打开了format.ts文件
- 它写了一个完美的日期格式化函数,连边界情况都考虑到了
- 它自动执行了git add src/utils/format.ts
- 它自动执行了git commit -m “feat(utils): add date format function”
- 它告诉我:代码已写入,并已提交,commit hash是a3f8b2c
整个过程不到三秒钟,我连“等一下”都没来得及说。
我当时就懵了,我寻思我没让你提交啊?我只是让你加个函数啊!你怎么自作主张就提交了?你经过我同意了吗?
后来我才知道,这整个过程没有任何硬编码规则,也没有任何预先注入的指令。Claude是自己判断出“我改完代码了,应该调用auto-commit这个Skill”的。
这就像你雇了个保姆,你跟她说“做完饭把碗洗了”,然后你让她炒个西红柿炒鸡蛋,她炒完之后,不用你说,自己就把碗洗了,还把灶台擦了。你说这保姆好不好?好是好,就是有时候会让你觉得有点瘆得慌。
二、静态注入:Claude偷偷给模型塞的小纸条
很多人以为,Claude知道有哪些Skill可用,是因为这些Skill写在了system prompt里。
大错特错。
Claude Code有个特别鸡贼的设计:它不在system prompt里预置任何Skill列表,而是通过一个叫attachment的东西,偷偷给模型塞小纸条。
这个attachment是什么呢?就是系统在每轮对话中,自动附加到消息列表里的额外内容片段,以system-reminder的形式注入。这个东西对用户界面是完全隐藏的,只有模型能看到。
就像你考试的时候,老师偷偷给学霸递了一张小抄,上面写着所有的考点,你在旁边干瞪眼,啥也不知道。
每次你给Claude发消息,系统在调用模型之前,都会先执行一个叫getAttachments()的函数。这个函数会收集各种附加信息,其中就包括skill_listing。
skill_listing是什么呢?就是当前所有可用Skill的列表。它长这样:
<system-reminder>
The following skills are available for use with the Skill tool:
# skills: 用户/项目 .claude/skills/
- auto-commit: 按 conventional commit 规范提交代码,当完成代码修改或新增文件后调用
# bundled: Claude Code 内置
- some-builtin-skill: ...
# mcp: MCP 服务器提供
- some-mcp-skill: ...
# plugin: 插件提供
- some-plugin-skill: ...
</system-reminder>
你看,它把所有来源的Skill都列出来了,每个Skill后面跟着它的描述。这个描述就是从SKILL.md的frontmatter里的description字段来的。
而且它还特别聪明,不会每次都把完整的列表塞给模型。它有个去重机制,第一次用户输入的时候,会把所有Skill都注入进去,之后每轮都会检查,只有当有新的Skill的时候,才会把新的Skill塞进去。
不然的话,每轮都塞一遍完整的列表,token早就烧没了。我见过有人项目里有50多个Skill,全量注入一次就要烧2000多个token,这谁顶得住啊?
我之前一直以为,Claude是天生就知道这些Skill的,结果翻了源码才发现,原来它也是靠小抄啊。我当时就笑了,合着搞了半天,AI也得靠作弊才能考好啊。
三、Skill工具:模型自己给自己发指令的自导自演
这里有个特别容易混淆的地方,很多人以为每个Skill都是一个独立的工具,模型可以直接调用。
根本不是。
模型眼里只有一个工具,叫SkillTool。这个工具的参数就两个:skill和args。
它的schema长这样:
{
"name": "Skill",
"description": "Execute a skill within the main conversation",
"input_schema": {
"type": "object",
"properties": {
"skill": { "type": "string", "description": "The skill name" },
"args": { "type": "string", "description": "Optional arguments" }
}
}
}
看到了吗?模型根本不知道每个Skill具体是干什么的,它只知道有个叫Skill的工具,给它传个名字和参数,它就能执行。
这就像你去饭店吃饭,你不用管后厨有多少个厨师,每个厨师擅长做什么菜。你只要跟服务员说“我要一份宫保鸡丁”,服务员自己会去找对应的厨师做。这个SkillTool就是那个服务员。
那SkillTool拿到名字之后干什么呢?它会去一个叫Command表的地方查找对应的Command。
哦,对了,我忘了说了,Skill和Command本质上是同一个东西。SKILL.md文件被createSkillCommand()函数解析之后,会直接返回一个Command对象。所以你在源码里看到的findCommand()、getSkillToolCommands()这些函数,其实都是在找Skill。
SkillTool找到对应的Command之后,会把SKILL.md里的内容作为用户消息注入到对话中。
划重点:SKILL.md的内容不是工具返回的文本,而是作为用户消息注入对话。
也就是说,模型调用完SkillTool之后,会收到一条“新用户消息”,内容就是SKILL.md里的指令。它会以为是你让它去执行git commit的,根本不知道是自己触发的。
我当时看到这里的时候,笑了整整五分钟。合着模型自己给自己发指令,自己执行,还以为是用户让它干的。这不是典型的自导自演吗?
那模型是怎么决定什么时候调用哪个Skill的呢?
它靠三个信息的组合:
- AGENTS.md中的行为指令:比如“改完代码后自动commit”
- SkillTool的工具提示:“发现匹配的Skill就必须调用,不要跳过”
- skill_listing中的短描述:比如“auto-commit: 按conventional commit规范提交代码,当完成代码修改或新增文件后调用”
这三个信息凑到一起,模型就会想:哦,我刚改完代码,AGENTS.md说改完代码要自动commit,skill_listing里有个auto-commit的Skill,正好是干这个的,那我就调用它吧。
你看,整个过程没有任何硬编码规则,全靠模型自己推理。这就是为什么Claude Code这么灵活,也是为什么它有时候会干出一些让你意想不到的事情。
四、动态注入:Skill集合变了怎么办?
有人可能会问:那我中途加了一个Skill,模型能知道吗?我删了一个Skill,模型还会记得吗?
答案是:能知道,但不是立刻知道;会记得,但调用会失败。
Claude Code的Skill集合不是静态的,你可以随时创建、修改、删除SKILL.md文件,系统会自动感知这些变化。
它是怎么感知的呢?
首先,Agent Loop每轮工具执行完毕后,都会再次调用getAttachments()函数,检查有没有新的附加信息需要注入。
也就是说,每次模型执行完一个工具,系统都会再检查一遍有没有新的Skill。如果有,就把新的Skill塞给模型。
其次,系统会监听文件系统,当SKILL.md文件被添加、修改或删除时,会触发防抖重载,重置去重记录。下一次检查的时候,就会把新的Skill列表塞给模型。
但是这里有个坑:已经注入到历史上下文里的旧skill_listing不会被“反向删除”。
也就是说,如果你删了一个Skill,模型可能还会记得那个Skill的存在,然后尝试调用它。这时候SkillTool会在当前Command表里查找失败,返回“Unknown skill”的错误。
这就像你把一个员工开除了,但是老板的通讯录里还留着他的电话,有事还会给他打电话,结果发现打不通。老板还会纳闷:哎,这个人怎么不接电话了?
还有一个更坑的地方:系统不会在启动时扫描整个项目树的Skill。它只会加载根目录下.claude/skills/里的Skill。子目录下的Skill,只有当模型操作到那个子目录下的文件时,才会被发现。
比如你的项目长这样:
my-project/
├── .claude/skills/auto-commit/SKILL.md ← 启动时已加载
└── packages/
└── core/
└── .claude/skills/core-lint/SKILL.md ← 启动时不知道这个目录存在
当模型读取packages/core/src/index.ts文件时,系统才会沿路径向上搜索,发现packages/core/.claude/skills/目录,加载core-lint这个Skill。
这个设计其实挺聪明的,它让项目不同模块可以自带独立的Skill,模型只在触碰到相关文件时才会发现它们,不会浪费token加载无关的Skill。
但是如果你想让模型一开始就知道所有的Skill,你就得把它们都放在根目录下的.claude/skills/里。不然的话,模型可能永远都不会发现它们。
五、语义匹配注入:当Skill多到烧不起token的时候
前面说的全量注入,在Skill数量少的时候没问题。但是当你的项目里有200多个Skill的时候,全量注入一次就要烧几千个token,这谁顶得住啊?
就像你去超市买东西,本来只想买瓶水,结果超市把所有商品的清单都给你念一遍,念完你都渴死了。
所以Claude Code搞了个实验性功能,叫skill_discovery。这个功能用Haiku模型做语义匹配,只注入与当前任务相关的Skill。
打开这个功能之后,原来的skill_listing会被裁剪,只保留bundled和MCP两类Skill。user/project/plugin的Skill,会由skill_discovery按需匹配注入。
skill_discovery有两条触发路径:
- 用户消息触发:用户发送消息时,在首次API调用前同步执行,把用户输入、对话历史和上下文喂给Haiku,Haiku返回匹配到的Skill列表。
- Agent Loop触发:Agent Loop每轮循环开始时,尝试启动异步预取。但只有当本轮有工具真正写入了文件时才会执行Haiku调用,否则直接跳过。
为什么只有写入文件时才触发?因为prod数据显示,早期实现每轮都调用Haiku,结果97%的调用什么都匹配不到。改成只在文件变更时触发,大幅减少了无意义的调用。
我当时看到这个数据的时候,笑了半天。合着97%的情况下,模型根本不需要任何额外的Skill,全量注入纯属浪费token。
打开skill_discovery之后,模型在同一轮对话中可能同时看到两条system-reminder:
# skill_listing 发的(始终存在,只是内容变少了)
The following skills are available for use with the Skill tool:
- graphify: any input to knowledge graph...
- sensight: 实时查询各平台热榜...
# skill_discovery 发的(按需出现)
Skills relevant to your task:
- article-publish-review: 对技术文章做发布前审查
These skills encode project-specific conventions. Invoke via Skill("<name>") for complete instructions.
如果skill_discovery没有匹配到任何Skill,整个attachment就会被过滤,不注入任何内容。
这个功能我亲测过,开了之后,token消耗直接降了一半,而且模型调用Skill的准确率还提高了。以前模型经常会调用一些不相关的Skill,现在只会调用跟当前任务相关的。
当然,模型也不是完全依赖自动注入。它还可以主动调用DiscoverSkillsTool来触发发现。比如当任务转向、遇到非常规工作流的时候,模型可以自己搜索系统可能遗漏的Skill。
六、设计取舍:每一个决策背后都是血泪史
最后跟大家聊聊Claude Code的设计取舍。每一个看起来很奇怪的决策,背后都是无数次踩坑踩出来的。
我给大家列几个最有意思的:
-
为什么首次用户输入时就注入skill_listing?
因为要确保模型从第一轮就知道有哪些Skill可用。不然的话,模型可能会自己想办法解决问题,而不是调用现成的Skill。就像你雇了个保姆,你不告诉她家里有洗衣机,她可能会用手洗所有的衣服。 -
为什么skill_listing要去重发送?
废话,不然每轮都重复通知,token早就烧没了。我见过有人因为没做去重,一天烧了1000美元的API账单,直接原地心梗。 -
为什么压缩后不重置skill_listing?
因为重新注入一次要4K token,而且那些几十轮都没用到的Skill,大概率跟当前任务没关系,重新注入纯属浪费。就像你搬家的时候,不会把十年都没穿过的衣服也带走一样。 -
为什么信任模型的长上下文注意力?
因为如果每轮都重新注入,token消耗会爆炸。这就是一个设计赌注,赌模型能记住第一轮注入的Skill列表。当然,这个赌注有时候会输,比如到了第50轮,模型可能就忘了还有某个Skill存在。这时候你只能手动用斜杠命令调用它。 -
为什么只在文件写入时触发skill发现?
因为prod数据显示97%的Haiku调用无结果。工具没有写文件的时候,模型大概率不需要任何新的Skill。这就像你去饭店吃饭,只有当你坐下点菜的时候,服务员才会给你菜单,不会你一进门就把菜单塞给你。
你看,这些设计取舍,没有一个是完美的。每一个都有它的优点和缺点。但是综合来看,这已经是目前最好的解决方案了。
七、写在最后
今天我把Claude Code的Skill动态发现机制扒了个底朝天。从静态注入到Skill工具,从动态注入到语义匹配注入,每一个环节我都给大家讲清楚了。
现在你应该知道,为什么你的AI会自动执行代码了。它不是成精了,也不是抽风了,是Skill动态发现机制在搞鬼。
其实这个机制设计得真的挺巧妙的。它没有用任何硬编码规则,全靠模型的推理能力来决定什么时候调用哪个Skill。这让Claude Code变得非常灵活,可以适应各种不同的项目和工作流。
当然,它也不是完美的。有时候它会调用不相关的Skill,有时候它会忘记某个Skill的存在,有时候它会干出一些让你意想不到的事情。但是总的来说,它已经比市面上所有的AI编程助手都要好用了。
我搞AI22年了,见过太多的技术来来去去。但是我觉得,Skill动态发现机制,可能是AI编程助手未来的发展方向。它让AI不再是一个简单的代码生成器,而是一个真正的编程助手,可以帮你完成整个开发流程。
最后,希望这篇文章能帮到大家。如果你也遇到过AI自动执行代码的情况,欢迎在评论区留言分享你的经历。
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)