如果你最近在用 Claude 做开发,肯定绕不开一个新东西:Claude Skills。简单说,它是一套让 AI 按需加载专属能力的扩展机制,你可以把一个领域知识、一组脚本、一套操作规范打包成“技能”,Claude 在遇到对应场景时自动调用,不用每次把规则重新说一遍。这篇文章想讲的,就是我实践下来最核心的那部分——怎么设计一个能被可靠触发的技能、怎么写 SKILL.md、怎么调试验证,以及那些文档里不会写但迟早会踩的坑。适合正在用 Claude 写代码、做自动化的开发者,哪怕你之前完全没接触过技能机制,读完也能照着搭出第一个能用的技能。
1. 先理解 Skills 是什么,解决的到底是什么问题
1.1 从“每次重申规则”到“按需调用能力”
用过一段时间 Claude 的人都会碰到一个尴尬:想让 AI 稳定执行一套特定流程,就得在对话里反复粘贴同样的说明,今天写一遍、明天写一遍,换一个会话又要重来。更难受的是,当说明太多太长,真正重要信息会被稀释,AI 反而抓不住重点。Skills 把“某个场景下的完整做法”固化成文件,放在约定的目录里,Claude 在需要的时候自己加载,不需要你在每一条消息里重复交代。
打个比方。以前你带新人,每次让他处理日志都得说一遍“先看错误码、再统计耗时、最后按模块归类”;后来你把流程写成一本岗位手册放在桌上,新人遇到日志相关问题自己会去翻手册。SKILL.md 就是那本手册,Claude 就是那个新人。手册只在需要的时候被翻开,而不是把 30 页内容全都摊在桌面上,这对上下文窗口是巨大的节约。
1.2 技能到底包含什么,和普通提示词有什么区别
一个技能本质上是一个目录,里面放着 SKILL.md 描述文件和若干辅助脚本、模板、参考数据。SKILL.md 里的 description 字段决定了“什么时候触发”,正文部分决定了“触发后按什么流程执行”,脚本则承担真正需要计算和处理的部分。
它跟普通提示词的核心差异不在格式,而在加载时机。提示词的约束条件在每一次请求里都完整地占用上下文;技能则只把 name 和 description 常驻在上下文中,正文部分等触发之后才加载。这就带来一个很实际的好处:你可以给 Claude 装备几十个技能,而它不需要在每次对话时把所有技能全文都读一遍。它只看到一排技能标签,哪个匹配当前问题,就把哪个的“说明书”展开。
1.3 技能与工具调用的边界
有人会拿 Skills 跟“工具调用”比较。它们解决的问题其实是两个维度:工具调用是让 AI 实时连接外部系统,比如查数据库、发请求、执行 Shell 命令,解决的是“AI 目前够不到这些资源”的问题;Skills 解决的是“AI 知道该怎么做但每次都要重新学”的问题。一个典型的组合是:技能里写好处理日志的完整流程和脚本,工具负责把日志文件读进来传给脚本。没有技能,工具只是给 AI 增加了一个盲目的操作入口,它不知道什么时候用最合适。
所以你在设计技能时要想清楚:这个技能到底是在教 AI“按什么步骤做事”,还是在给它“连接某个系统的能力”。前者放技能,后者更适合放到工具/MCP 那边。把两者混在一起,是后面很多问题的根源。
2. 写第一个技能:目录结构、SKILL.md 与脚本
2.1 目录到底放哪里:项目级、用户级与临时目录
技能目录有几种放置位置,决定它的生效范围。如果你希望技能跟着某个项目走,让所有打开这个项目的人都能用,放在项目根目录下的.claude/skills/里;如果你希望自己电脑上所有项目都能用,放在用户目录~/.claude/skills/;如果只是临时想试一下某个技能,或者从别人那里收到一个装技能文件的文件夹,在支持的客户端里拖入目录就能临时加载。项目级技能会覆盖用户级同名技能,这个优先级顺序在排查“为什么改了没生效”时很重要。
目录名也有讲究,只能用英文小写字母、数字和连字符,例如log-analyzer合法,日志分析器、LogAnalyzer都不规范。文件扫描是按目录名识别技能的,目录名同时也会作为技能名称,尽量取能望文生义的名字。
2.2 SKILL.md:一份给 AI 看的执行手册
SKILL.md 是技能的核心文件,用 Markdown 编写,分两部分:文件开头的 frontmatter 配置区,和正文部分。frontmatter 里目前最关键的字段是name和description,它们决定了技能的标识和触发条件。body 里是给 AI 看的具体操作说明。
以下是我实践下来常用的一个模板:
--- name: log-analyzer description: 用于分析 JSON 格式的日志文本或日志文件,输出日志级别分布、请求耗时统计和错误类型排序。当用户提出“日志分析”“统计报错”“看请求耗时”等需求时使用。 --- # 日志分析技能 ## 目的 对服务端 JSON 日志快速做统计与归类,辅助排查问题。 ## 适用场景 - 用户粘贴一段日志,希望知道里面主要有哪些错误 - 用户希望统计请求耗时分布、错误类型占比 - 用户需要把日志按级别归类呈现 ## 使用方法 1. 确认输入数据是 JSON 格式;如果是 JSON Lines,先用脚本转换成 JSON 数组。 2. 将日志内容保存为临时文件,或直接通过标准输入传给脚本。 3. 使用 `python3 log_analyzer.py < input.json` 运行脚本。 4. 把输出结果整理成简洁的结构化报告,不要贴原始脚本输出。 ## 输出格式 - 总条目数 - 日志级别分布 - 平均耗时、P50/P95 耗时 - 出现频率最高的错误类型 Top 5 ## 限制 - 只做统计,不负责根因分析 - 不处理非 JSON 格式日志注意看 description 的写法。它没有写“这是一个日志分析技能”这种宽泛描述,而是写清楚“输入是什么、输出什么、哪些场景会用到”。Claude 是靠 description 来判断要不要触发这个技能的,描述写得越具体,触发越准;写得越抽象,它越可能在不该用的时候用,或者在该用的时候没用。
2.3 技能里怎么放脚本、数据和模板
技能目录里除了 SKILL.md 还可以放脚本、模板、参考数据。脚本语言不局限,Python、Shell、Node.js 都行,重点是 SKILL.md 里的调用方式要写清楚,包括参数、输入输出格式、工作目录假设。如果你在多个项目里复用同一个技能,脚本里的相对路径很容易出问题——SKILL.md 里写脚本路径时建议用相对于技能目录的说明,也可以把脚本设计成只从标准输入读数据、把结果打印到标准输出,尽量减少对文件路径的依赖。
模板和数据文件的用法也很常见。比如你希望 AI 按照特定格式输出报告,就在技能里放一份report_template.md,SKILL.md 里写明“输出报告时必须引用该模板”。这比把格式规则硬塞进文字说明可靠得多,模板本身可以作为参照物被加载。
3. 实操:从零构建一个日志分析技能
3.1 先明确需求:这个技能要闭环解决什么问题
任何技能都该从一个具体的使用场景出发。这次我给自己定的目标是:能自动分析服务端 JSON 日志,输出一份包含日志量、级别分布、耗时统计、错误分类的报告。这个需求在排查线上问题时反复出现,值得固化成技能。
选定目录结构:
.claude/skills/ └── log-analyzer/ ├── SKILL.md └── log_analyzer.py3.2 写能稳定运行的脚本
脚本设计遵循一个原则:只从标准输入读数据,只往标准输出写结果,不硬编码任何路径。这样无论 Claude 在哪个目录下执行,都不会因为找不到文件而失败。我用的处理逻辑是:读入 JSON 数组,统计日志级别、耗时指标、错误类型。
#!/usr/bin/env python3 import json import sys import statistics from collections import Counter def main(): raw = sys.stdin.read() try: records = json.loads(raw) except json.JSONDecodeError as e: print(f"错误:输入不是合法 JSON。{e}") sys.exit(1) if isinstance(records, dict): records = [records] total = len(records) levels = Counter(r.get("level", "unknown") for r in records) durations = [r["duration_ms"] for r in records if isinstance(r.get("duration_ms"), (int, float))] print(f"总条目数: {total}") print(f"日志级别分布:") for level in ["debug", "info", "warn", "error", "fatal"]: if levels.get(level): print(f" {level}: {levels[level]}") if durations: durations.sort() p50 = durations[len(durations) // 2] p95 = durations[int(len(durations) * 0.95) - 1] print(f"耗时统计: P50={p50:.1f}ms, P95={p95:.1f}ms, max={max(durations):.1f}ms") error_types = Counter(r.get("error_type", "unknown") for r in records if r.get("level") == "error") if error_types: print("错误类型 Top 5:") for err, cnt in error_types.most_common(5): print(f" {err}: {cnt}") if __name__ == "__main__": main()这个脚本故意做得很简单,但足够演示核心写法:可重复运行、清晰的错误提示、结构化的输出。你完全可以根据自己的场景往里面加逻辑,比如按服务名分组、过滤某个时间窗口、识别特定错误码等。重点不是脚本多复杂,而是它必须能在干净环境里一次跑通。
3.3 用“可测试的描述”校准触发时机
SKILL.md 写好后,最难调的不是正文,而是 description。我自己的经验是,写完先拿它做一次“搜索测试”:把自己当成 Claude,模拟几个真实请求,然后判断这个描述会不会让你想打开这个技能。
比如下面两个描述对比:
- 差的描述:“分析日志”
- 好一点的描述:“用于分析 JSON 格式的日志文本或日志文件,输出日志级别分布、请求耗时统计和错误类型排序。当用户提出‘日志分析’‘统计报错’‘看请求耗时’等需求时使用。”
差的描述问题在于“日志”这个词太常见,AI 很容易误触发;而且它没说明能输出什么,遇到该用时也未必能联想到。好一点的描述有价值主张、有输入输出定义、有触发词提示,误触发率和漏触发率都会大幅下降。
3.4 在 SKILL.md 中写清楚“输出格式”,让结果可被继续使用
技能的输出不只是给用户看的报告,有时候还会作为下一轮对话的输入。你要在 SKILL.md 里明确指定输出格式。日志分析技能我要求输出固定格式的报告:
- 先说总条目数和统计时间范围
- 再列级别分布
- 然后是耗时指标
- 最后是错误类型清单
这个顺序是固定的,Claude 每次输出都会按照这个结构来。之所以要固定,是因为当后面还想让 Claude 基于分析结果做进一步处理,比如自动发提醒、生成工单,稳定结构的数据比自由发挥的文本更容易被解析和处理。
4. 调试与验证:技能有没有真正被触发
4.1 怎么确认技能已经被加载
写完技能之后,最想搞明白的事情就是:Claude 到底有没有把它加载进来。不同客户端有不同的检查方式。在命令行环境里通常可以用斜杠命令查看已加载的技能列表,比如/skills;在桌面端和移动端,一般能在会话面板里看到当前加载的技能标签。如果你用的是一个不直接暴露技能列表的界面,可以开一个全新会话,问一句“你现在加载了哪些技能”,它会告诉你它看到了什么。
这一步非常关键。我见过不少人写完技能,兴冲冲地提需求,结果 AI 压根没加载它,所有调试都是白费。先确认加载状态,再讨论触发效果,顺序不能反。
4.2 通过固定提示词做触发验证
确认加载后,下一步是验证触发逻辑。固定一组测试提示词,每次改动 description 后都跑一遍相同的用例,对比触发结果。比如我给日志分析技能准备了三句测试:
- “帮我看一下这段日志里都有什么错误”
- “统计一下请求耗时”
- “这段内容是 JSON,你能帮我分析一下吗”
第一、二句应该触发技能,第三句不应该触发。如果第二句没触发,说明 description 里缺少“请求耗时”这个触发场景;如果第三句触发了,说明描述写得过于宽泛,需要收紧。把测试结果记录下来,你会慢慢发现规律:什么时候该用,什么时候不该用,其实完全由 description 决定。
4.3 验证脚本在技能中的可执行性
脚本能不能在技能环境中正常工作,也需要单独验证。别在能正常运行的终端环境里测一次就以为万事大吉。我有一次遇到的情况是:SKILL.md 里写python3 log_analyzer.py,但 Claude 执行时并没有把输入数据成功传给脚本。后来改成只从标准输入读取,并把调用方式在 SKILL.md 里写得更明确,问题才解决。
更稳妥的做法是,在脚本开头加一段自检逻辑:文件不存在、数据格式不对、字段缺失,分别给出明确报错。报错信息越具体,Claude 在运行时就越容易根据错因自我修正,而不是懵在原地。你甚至可以加一个--selftest参数,跑一条内置示例数据来验证脚本环境正常。
4.4 修改后没生效:先清上下文再试
这是最容易踩的坑:你改了 SKILL.md,然后在同一个会话里继续测试,发现还是旧行为。原因是技能加载往往发生在会话启动阶段,已经加载进上下文的技能定义不会因为文件改动而动态刷新。遇到这种情况,不要怀疑自己改错了,先开一个新会话再测。修改 SKILL.md 的标准流程是:改文件 → 检查格式 → 新开会话 → 跑测试用例。
5. 发布与团队复用:把技能当工程资产维护
5.1 一个技能的可发布检查清单
技能做出来自己用是一回事,拿出来给团队共享是另一回事。别人不会知道你在写的时候脑补了哪些约束条件,所以发布前需要做一次彻底检查。我自己整理了一份简短清单:
- 目录名和 frontmatter 里的 name 合法且一致,只用小写字母、数字和连字符
- description 不只写“是什么”,还写“什么场景该用”“能产出什么结果”
- SKILL.md 里的步骤没有遗漏假设条件,比如“先安装依赖”“先转成 JSON”
- 脚本可以从标准输入读取、可重复执行,且输出格式稳定
- 技能文件里没有硬编码的绝对路径或个人敏感信息
- 依赖项被完整列出,最好用一个
requirements.txt之类文件声明
对照这份清单走一遍,至少能把“只有我自己会用”变成“能给别人用”。这也是技能从个人脚本升级成团队资产的分水岭。
5.2 技能的版本管理:像代码一样维护
SKILL.md 本质上也是代码,需要走版本管理。技能进入团队目录后,大概率会遇到这种情况:设备上线了一个新错误类型,希望分析结果里多一列统计;有人发现某个触发词会让 Claude 在无关场景误触发,需要修正 description。这时候没有版本记录,你根本不知道上一次的改动对触发准确率产生了什么影响。
我建议团队的每个技能目录里都维护一个简短的 CHANGELOG,记录每次改动的动机和验证结果。技能本身的变更很容易验证,固定测试用例跑一遍就有结论。这项习惯并不复杂,但长期维护价值极大。
5.3 多技能在上下文中的“存在感”管理
当一个项目里装了二三十个技能时,你会开始感受上下文占用的压力。虽然技能的 description 已经比完整手册精简很多,但几十个 description 叠加起来依然会挤占有限的上下文空间。我的处理原则是:只在技能真正被使用时,它才值得被完整提起;那些低频技能,描述写得再精简,也依然会造成噪音。
控制方式很简单,低优先级的技能不要放在最容易被扫描到的位置;业务相关的能力优先放项目级目录;跨项目通用的能力放用户级目录。别让所有技能都挤在同一个项目里,否则上下文里一半是技能描述,尽做一些无关判断,效率反而更低。
6. 常见问题速查与避坑心得
6.1 问题速查表:现象、排查方向和处理办法
以下是根据实际经验整理的高频问题,按“现象→排查→处理”给出方向:
| 现象 | 排查方向 | 处理办法 |
|---|---|---|
| 技能完全没有被调用 | 先确认技能是否已加载,再检查 description 是否包含触发场景 | 新开会话后用 /skills 检查;把 description 改写得更具体 |
| 无关场景里误触发技能 | description 里某些词过于通用,比如“分析”“查询” | 给描述增加限制条件,写明“仅当……时使用” |
| 脚本执行报错,找不到文件 | SKILL.md 中路径假设与实际执行目录不一致 | 脚本改为只读标准输入;显式声明工作目录 |
| 改了 SKILL.md 但行为没变化 | 技能可能是在会话开始时加载的,旧定义还在生效 | 开新会话再测;不要在旧会话里反复修改验证 |
| 多个技能内容相似,经常选错 | 两个技能的 description 边界模糊 | 重新划分场景边界,各自突出独有的触发信号 |
| 技能输出不稳定,格式漂移 | SKILL.md 没有强约束输出结构 | 在正文里加入固定输出模板,并要求严格按模板输出 |
6.2 最大的坑:脚本越写越复杂,技能就越容易失控
我在做技能的过程中一个很深刻的体会是:你不需要把脚本写成神级工具。一个技能最理想的状态,是脚本只做好它该做的那一件事,然后把结果交给 Claude 去解释和延展。一旦把太多判断逻辑塞进脚本,你会面临数不清的边界条件,而且脚本本身就成了黑盒,Claude 失败了你也很难定位到底是脚本问题还是 prompt 问题。
技能设计遵循一种“渐进式披露”的原则。设想一下,如果技能每次触发都会把 30 页说明、10 个模板、6 个脚本全部加载进去,那么核心流程反而会被淹没,触发成本也会高不可测。你应该把常驻内容压缩到极简,让 SKILL.md 正文像目录一样只展示最核心的顺序和优先级,把深水区的细节藏进子文件,在“需要时再拉出来”——这就是一套很自然的权重控制策略。
6.3 技能与 MCP、工具的关系:先画流程图再动手
最后想提醒一件事:动手写技能前,先画一张简单的流程图,把“用户说了什么 → 触发哪个技能 → 技能调用什么工具 → 结果怎么反馈”画清楚。很多人写着写着就发现,日志分析这个需求里的数据读取完全可以用已有工具完成,但脚本里又重复实现了一遍文件读取逻辑,结果造成了职责重叠。
实操中我发现,技能和工具配合得当,效果会非常惊艳:技能提供“什么时候做、按什么顺序做”的智力路线,工具提供“怎么连上数据库、怎么取到数据”的执行能力。两者画在同一张流程图上时,边界自然就清楚了。你的技能负责教会 Claude 一套动作序列和判断准则,工具负责让这套序列真正落到系统上,这类组合的价值远大于单向加技能或单向加工具。
写在最后
说实话,Claude Skills 这个机制我一开始觉得不过又是一个提示词管理工具,真正用起来之后才意识到它的价值在于“把人的经验沉淀成可复用、可验证的资产”。技能不是简单地把一段话存下来,它是一整套工作流的最小封装单元。从设计 description 到调试触发逻辑,从写脚本到验证输出,每一步都有讲究,也都需要耐心打磨。
最后分享三个我从实践中提炼的小建议:第一,description 花的时间应该占整个技能开发的一半以上,触发不准的技能等于没有技能;第二,维护一套固定测试提示词,每次修改后都跑一遍,这是衡量改动是否有益的唯一标准;第三,别贪多,一个技能只负责解决一类问题,边界清晰才是它最大的优点。按这个思路慢慢积累,你会看到自己手头那堆反复粘贴的提示词,最终都变成了一个个安静的、随时待命的技能。