1. 从"skills"这个词被玩坏说起
如果你最近在技术社区里频繁看到"skills"这个词,第一反应可能是招聘JD里的技能要求,或者是游戏里的技能树。但在AI编程助手这个圈子里,skills已经变成了一个非常具体的概念——它是Claude Code、Codex这类AI编程工具的能力扩展单元,本质上就是一套预定义好的指令集、工具调用逻辑和上下文模板的组合包。
我最初接触这个概念的时候也懵了很久。官方文档写得云里雾里,社区里的讨论又散落在各种issue和discord频道里,真正能讲清楚"skills到底是什么、怎么用、什么时候该自己写"的内容少得可怜。更麻烦的是,Claude Code和Codex这两个主流工具对skills的实现方式还不完全一样,网上的教程经常把两者混着讲,照着做很容易踩坑。
这篇内容就是把我这段时间折腾skills的经验整理出来。不管你是刚装好Claude Code想试试水的新手,还是已经在用Codex写代码但觉得默认能力不够用的老手,或者你是想给自己团队搭一套标准化AI工作流的技术负责人,下面这些内容应该都能帮你少走点弯路。我会从skills的核心机制讲起,然后分别拆解Claude Code和Codex两个平台上的实操方法,最后聊聊怎么设计一套真正好用的自定义skills。
需要提前说明的是,skills这个概念还在快速演进中,不同版本的工具对skills的支持程度差异很大。我下面提到的操作步骤和配置方式,都是基于我实际跑通的版本,如果你用的是更早或更新的版本,细节上可能需要微调。
2. skills到底解决了什么问题
2.1 从"每次都要重新解释"到"一次定义反复使用"
在没有skills之前,我们用AI编程助手的工作流大概是这样的:打开对话框,把项目背景、代码规范、技术栈约束、输出格式要求全部打一遍,然后才进入正题。下次开个新会话,这些内容又得重新说一遍。虽然有些工具支持项目级的配置文件,但那个配置是全局的,没法针对不同任务类型做差异化。
skills的核心价值就在于把"提示词工程"这件事从一次性操作变成了可复用资产。你可以把一套针对特定场景的指令、工具权限、上下文模板打包成一个skill,之后在任何会话里通过一个简短的调用就能激活它。比如你有一个专门用来做代码review的skill,里面定义好了review的检查清单、输出格式、严重程度分级标准,那以后每次review只需要说"用review skill"就行了。
这个思路其实和传统开发里的"函数封装"是一回事。你把重复的逻辑抽出来做成函数,需要的时候调用就行,不用每次都把那段代码复制粘贴一遍。skills就是AI编程场景下的函数封装。
2.2 skills和普通提示词模板的本质区别
很多人会问:这不就是保存了几个提示词模板吗?我自己建个文档存着不也一样?
区别在于执行层面。普通的提示词模板你复制粘贴进去,AI只是把它当普通文本处理。但skills是工具层面的原生支持,它可以在激活时动态加载额外的工具权限、注入特定的系统指令、甚至改变AI的行为模式。举个例子,一个普通的提示词模板没法让AI自动去读某个特定目录下的文件,但一个配置了文件读取权限的skill可以。
另一个关键区别是作用域管理。skills可以定义自己的作用范围——有些skill只在特定项目里生效,有些是全局的,有些甚至可以嵌套调用其他skill。这种层级化的管理能力是纯文本模板做不到的。
2.3 哪些场景最适合用skills
根据我的实际使用经验,下面这几类场景用skills的收益最明显:
- 重复性高的标准化任务:比如代码格式化检查、commit message生成、单元测试模板生成。这类任务每次的输入不同但处理逻辑固定,非常适合封装成skill。
- 需要特定领域知识的任务:比如你团队有一套内部的API设计规范,或者你们用的某个框架有特殊的约定。把这些知识写进skill里,AI就不会再给出不符合你们规范的代码了。
- 多步骤的复杂工作流:比如"先分析需求,再设计接口,然后生成代码,最后写测试"这样的流程。你可以把每个步骤拆成独立的skill,然后通过一个主skill来编排调用顺序。
- 需要严格控制输出格式的场景:比如生成API文档、写数据库迁移脚本这类对格式要求很高的任务。
反过来,如果你只是偶尔用一次AI助手,或者每次的任务都完全不同没有规律,那花时间写skill的投入产出比就不太高。
3. Claude Code上的skills实操拆解
3.1 安装和基础环境确认
在开始配置skills之前,得先确保Claude Code本身装好了。Windows用户和macOS/Linux用户的安装方式略有不同,我分别说一下。
macOS和Linux上最简单的方式是通过npm安装:
npm install -g @anthropic-ai/claude-codeWindows上如果你用的是WSL,那和Linux一样。如果是在原生Windows环境下,建议先装好Node.js和npm,然后同样用上面的命令。装完之后在终端里跑一下claude --version确认安装成功。
这里有个容易踩的坑:很多人装完之后发现命令找不到,大概率是npm的全局bin目录没有加到PATH里。你可以用npm config get prefix看一下npm的全局安装路径,然后确认这个路径下的bin目录在PATH环境变量里。
环境确认没问题之后,第一次运行claude会引导你做登录和初始化配置。这一步按提示走就行,没什么特别的。
3.2 skills的目录结构和加载机制
Claude Code的skills存放在特定目录下,加载机制遵循"就近原则"。具体来说,它会从以下几个位置按优先级查找skills:
- 当前项目的
.claude/skills/目录 - 用户主目录下的
~/.claude/skills/目录 - 系统级的skills目录(一般用不到)
项目级的skills优先级最高,这意味着你可以在不同项目里定义同名的skill,Claude Code会优先使用当前项目下的版本。这个设计很合理,因为不同项目的技术栈和规范往往不一样。
每个skill是一个独立的目录,目录名就是skill的名称。目录里面至少需要一个SKILL.md文件,这是skill的核心定义文件。除此之外还可以放一些辅助文件,比如模板文件、配置数据等。
一个典型的skill目录结构长这样:
.claude/skills/ code-review/ SKILL.md templates/ review-template.md api-design/ SKILL.md references/ rest-conventions.md3.3 写一个能用的SKILL.md
SKILL.md是整个skill的核心,它的格式是带YAML frontmatter的Markdown文件。frontmatter部分定义元数据,正文部分定义具体的行为指令。
一个最基本的SKILL.md长这样:
--- name: code-review description: 对指定代码进行结构化review,输出问题清单和改进建议 --- 你是一个资深代码审查员。当用户要求你review代码时,按以下步骤执行: 1. 先通读代码,理解整体逻辑 2. 检查以下维度: - 边界条件处理 - 错误处理完整性 - 命名规范 - 潜在的性能问题 3. 按严重程度分级输出问题:Critical / Major / Minor 4. 每个问题给出具体的修改建议frontmatter里的name字段是skill的唯一标识,调用时用的就是这个名称。description字段很重要,它决定了Claude在什么情况下会自动建议使用这个skill。写得越具体,触发越精准。
正文部分就是实际的指令内容。这里有个经验:指令要写得像你在给一个新人做onboarding,把背景、目标、步骤、注意事项都说清楚。不要假设AI"应该知道"某些上下文,该写的都写上。
3.4 调用和调试skill的几种方式
写好skill之后,调用方式有几种:
第一种是显式调用,直接在对话里说"用code-review这个skill来检查这段代码"。这种方式最直接,适合你明确知道要用哪个skill的场景。
第二种是隐式触发,Claude会根据你的请求内容和skill的description自动判断是否调用。比如你说"帮我看看这段代码有没有问题",如果code-review的description写得好,Claude可能会自动激活它。
第三种是通过其他skill间接调用。你可以在一个skill的指令里写"调用xxx skill来完成这一步",实现skill之间的编排。
调试skill的时候,我建议先用最简单的输入测试。比如code-review skill,先拿一段有明显问题的代码去试,看看它能不能准确识别出问题并按预期格式输出。如果输出不符合预期,就回去改SKILL.md里的指令,反复迭代几次就能调到一个比较稳定的状态。
注意:修改SKILL.md之后不需要重启Claude Code,但需要开一个新的会话才能生效。当前会话里已经加载的skill定义不会自动刷新。
4. Codex平台上的skills配置路径
4.1 Codex的skills体系和Claude Code的差异
Codex的skills机制和Claude Code在设计理念上相似,但实现细节差别不小。最明显的区别是Codex更强调skills的可组合性,它允许你在一个skill里引用其他skill作为依赖,形成一棵skill树。这个设计对于构建复杂工作流很有用,但也增加了配置的复杂度。
另一个差异是Codex的skills配置更偏向声明式。Claude Code的SKILL.md主要是自然语言指令,而Codex的skill配置里会有更多结构化的字段,比如明确的输入参数定义、输出格式schema、工具权限列表等。这让Codex的skills更适合做自动化流水线,但写起来也更啰嗦。
4.2 配置文件的位置和格式
Codex的skills配置一般放在项目的.codex/skills/目录下,全局配置在~/.codex/skills/。和Claude Code类似,项目级配置优先于全局配置。
Codex的skill定义文件通常是一个YAML或JSON文件,具体格式取决于你用的Codex版本。以YAML为例,一个基本的skill定义大概是这样:
name: test-generator description: 根据函数签名自动生成单元测试 version: 1.0 inputs: - name: source_file type: file_path required: true - name: framework type: string default: pytest tools: - file_read - file_write prompt: | 读取指定的源文件,分析其中的函数和类定义, 为每个公开函数生成对应的单元测试。 使用{framework}作为测试框架。 测试要覆盖正常路径和边界条件。这个结构比Claude Code的SKILL.md要严格得多。inputs字段定义了skill接受哪些参数,tools字段声明了skill需要哪些工具权限,prompt字段才是实际的指令内容。
4.3 从零跑通一个Codex skill的完整流程
我拿一个实际例子来演示。假设我要写一个skill,功能是"读取当前项目的package.json,分析依赖版本,给出升级建议"。
第一步,创建skill目录和配置文件:
mkdir -p .codex/skills/dep-checker然后在.codex/skills/dep-checker/skill.yaml里写入定义:
name: dep-checker description: 分析项目依赖,检查版本更新和潜在冲突 version: 1.0 inputs: - name: package_file type: file_path default: ./package.json tools: - file_read - shell_exec prompt: | 1. 读取{package_file},提取所有依赖及其版本号 2. 对每个依赖,检查是否有已知的安全漏洞 3. 检查依赖之间是否存在版本冲突 4. 按优先级输出建议:安全更新 > 功能更新 > 可选更新 5. 对每个建议给出具体的版本号和变更说明第二步,在Codex会话里激活这个skill。具体命令取决于你的Codex版本,一般是通过/skill dep-checker或者类似的语法来调用。
第三步,观察输出并迭代。第一次跑大概率会有各种小问题,比如路径解析不对、输出格式不符合预期等。根据实际输出调整prompt部分,直到稳定。
4.4 Codex skills常见的配置报错和处理
在配置Codex skills的过程中,我遇到过几类典型报错:
报错一:unrecognized configuration setting。这个通常是因为你的skill.yaml里写了当前版本不支持的字段。Codex对配置字段的校验比较严格,多一个不认识的字段就会报错。解决办法是查对应版本文档,把不支持的字段删掉。
报错二:工具权限不足。如果你在tools字段里声明了某个工具但实际没有权限,skill执行到那一步会失败。这时候需要检查你的Codex配置里是否开启了对应的工具权限。
报错三:输入参数类型不匹配。Codex对inputs的类型检查比较严格,如果你声明了type: file_path但传了一个不存在的路径,会直接报错而不是给一个友好的提示。调试的时候建议先用绝对路径测试。
提示:Codex的skill配置改动后,一般需要重新加载配置才能生效。具体命令看你的版本,通常是
/reload或者重启会话。
5. 设计一套真正好用的自定义skills
5.1 从"我每天都在重复说什么"开始
设计skills的第一步不是打开编辑器写配置,而是观察自己的工作流。你可以花一周时间记录一下:每天用AI助手的时候,有哪些指令是你反复输入的?有哪些上下文是你每次都要重新解释的?有哪些输出格式是你每次都要纠正的?
这些重复出现的模式就是skill的候选。我的经验是,如果一个指令你一周内输入了超过三次,就值得考虑把它封装成skill。
但也不是所有重复指令都适合做成skill。判断标准是:这个指令的逻辑是否稳定?如果每次的输入差异很大,处理逻辑也完全不同,那做成skill反而会增加调用时的复杂度。适合做skill的是那种"输入不同但处理逻辑固定"的任务。
5.2 skill的粒度控制:太粗和太细都是坑
这是我在实际踩坑之后最有感触的一点。刚开始写skill的时候,我倾向于写"大而全"的skill,一个skill里塞了十几个步骤,覆盖从需求分析到代码生成到测试的完整流程。结果就是:这个skill很难调试,因为出问题的时候你很难定位是哪一步的指令写得不好;而且复用性很差,因为大部分场景下你只需要其中某几个步骤。
后来我调整了策略,改成"小而专"的skill,每个skill只做一件事,但把这件事做到极致。比如把"代码review"拆成"安全检查"、"性能检查"、"可读性检查"三个独立skill。这样每个skill的指令可以写得很精细,调试也容易,而且可以按需组合。
但粒度也不能太细。如果一个skill只做"检查变量命名"这一件事,那调用它的成本可能比直接让AI检查还高。我的经验法则是:一个skill应该对应一个完整的、有独立价值的任务单元。判断标准是,这个skill的输出是否可以独立交付?如果可以,那粒度就合适。
5.3 让skill的输出稳定可控的几个技巧
skill用久了你会发现,最大的挑战不是让AI做对一次,而是让它每次都做对。同样的skill,今天跑出来的结果和明天跑出来的可能差别很大。要提高输出的稳定性,我总结了几个实用技巧:
技巧一:用结构化输出格式。在skill指令里明确要求AI按特定格式输出,比如JSON、Markdown表格、或者固定的段落结构。格式约束越明确,输出的随机性越小。
技巧二:给出正反例。在skill指令里放一两个"好的输出示例"和"不好的输出示例",让AI有明确的参照。这比单纯用文字描述要求有效得多。
技巧三:分步骤执行。把复杂任务拆成明确的步骤,要求AI按顺序执行,每步输出中间结果。这样即使最终结果有问题,你也能看到是哪一步跑偏了。
技巧四:设置检查点。在关键步骤后加一个自检环节,让AI自己检查上一步的输出是否符合要求。这个自检指令要具体,不能只说"检查一下",而要说"检查输出是否包含以下字段:xxx、yyy、zzz"。
5.4 skill的版本管理和团队协作
当你写了一堆skill之后,版本管理就成了问题。我的做法是把skills目录纳入git管理,和项目代码一起提交。这样每次skill的改动都有记录,出问题可以回滚,团队成员也能共享同一套skill。
对于团队协作场景,有几个实践建议:
- 把通用的skill放在项目仓库的
.claude/skills/或.codex/skills/目录下,随代码一起分发 - 个人偏好的skill放在用户主目录下,不纳入版本控制
- 在skill的description里标注适用场景和维护者,方便团队其他成员理解
- 定期review skill的使用情况,把没人用的skill清理掉
另外,skill的命名要有一套统一的规范。我一般用动词-名词的格式,比如review-code、generate-test、analyze-deps。这样从名字就能看出这个skill是干什么的,不用点进去看内容。
6. 那些文档里不会写的踩坑记录
6.1 skill不生效的排查链路
skill写了但没生效,这是最常见的问题。我整理了一个排查顺序,按这个顺序走基本能定位到原因:
第一步,确认skill文件的位置对不对。项目级skill必须在.claude/skills/或.codex/skills/下,目录名要和skill的name字段一致。我遇到过好几次是因为目录名拼写错误导致skill加载不到。
第二步,检查SKILL.md或skill.yaml的格式。YAML对缩进非常敏感,一个空格不对就可能导致解析失败。建议用专门的YAML校验工具检查一下。
第三步,确认是否需要重新加载。大部分情况下修改skill后需要开新会话才能生效,当前会话不会自动刷新。
第四步,看description是否足够具体。如果description写得太泛,AI可能不会在合适的时机触发这个skill。试着把description写得更具体,包含明确的触发关键词。
第五步,检查是否有同名skill冲突。如果项目级和全局都有同名的skill,可能会出现预期外的行为。建议用claude skills list或类似命令确认当前加载了哪些skill。
6.2 跨平台使用的兼容性问题
如果你同时在Windows、macOS和Linux上工作,skill的跨平台兼容性是个需要注意的问题。最典型的是路径分隔符:Windows用反斜杠,Unix系用正斜杠。如果你的skill指令里硬编码了路径,换平台就可能出问题。
解决办法是在skill指令里用相对路径,或者用平台无关的路径表示方式。另外,如果skill里涉及shell命令,要注意不同平台的命令差异。比如ls在Windows的cmd里是不存在的。
还有一个坑是换行符。Windows用CRLF,Unix用LF。如果你的skill里有模板文件,在不同平台间同步时可能会出现换行符不一致的问题。建议在git配置里设置core.autocrlf来统一处理。
6.3 skill之间的依赖和冲突处理
当skill数量多起来之后,skill之间的依赖和冲突就不可避免了。我遇到过的情况包括:两个skill都试图修改同一个文件、一个skill的输出格式和另一个skill的输入要求不匹配、skill A调用了skill B但B在当前环境下不可用。
处理这些问题的原则是:尽量让skill保持独立,减少相互依赖。如果确实需要组合使用,就在skill指令里明确声明依赖关系,并在调用前检查依赖是否满足。
对于输出格式的匹配问题,我建议在skill设计阶段就定义好标准的输入输出格式,所有skill都遵循同一套规范。这样组合使用时就不用做格式转换了。
6.4 性能优化:别让skill拖慢你的工作流
skill用多了之后,你可能会发现AI的响应变慢了。这是因为每次调用skill都需要加载额外的上下文和指令,skill越复杂,加载开销越大。
优化的思路有几个:一是精简skill指令,去掉不必要的背景说明,只保留核心的操作指令;二是把大skill拆成小skill,按需加载而不是一次性全部加载;三是对于不常用的skill,改成手动调用而不是自动触发,减少不必要的加载。
另外,如果你的skill里引用了外部文件(比如模板文件、参考文档),要注意这些文件的读取也会消耗时间。对于不常变动的参考内容,可以考虑直接内联到skill指令里,避免每次读取文件的开销。
7. 我目前的工作流和几点个人体会
经过这段时间的折腾,我现在的skills使用策略大概是这样的:全局层面维护一套通用的基础skill,包括代码review、commit message生成、文档格式化这几个高频场景。项目层面针对具体技术栈维护专用skill,比如React项目的组件生成skill、Python项目的测试生成skill。临时性的任务就不做成skill了,直接对话解决。
有一个体会特别深:skill的质量不取决于你写了多少,而取决于你删了多少。我最初写的skill有二十多个,现在精简到了八个,但实际使用频率和效果反而更好了。因为每个保留下来的skill都是经过反复打磨的,指令精准、输出稳定,用起来放心。
另一个体会是,skill不是写完就完了,需要持续迭代。每次用的时候如果发现输出不符合预期,就顺手改一下skill指令。积累几次之后,skill就会越来越贴合你的实际需求。这个过程有点像训练一个助手,你给它的反馈越多,它就越懂你。
最后说一个可能有点反直觉的观点:不要试图用skill解决所有问题。有些任务就是适合每次手动描述,因为它们的上下文差异太大,强行做成skill反而会增加认知负担。skill最适合的是那些"高频、稳定、可标准化"的任务,其他的还是交给即兴对话更合适。