Codex Skills 配置实战:我装了 30 多个 Skill,只有 7 个真正改变了工作流——附完整 config 踩坑记录
上个月我把 Codex Agent 当主力用,Skills 生态刚起来那会儿什么都往里塞,高峰期 .codex/config.yaml 里注册了 34 个 Skill。然后月底一看账单——$187,比上个月翻了将近一倍。排查了两天才发现,有些 Skill 的 description 写得太模糊,导致 Agent 几乎每轮对话都会触发它们做一次"试探性调用",白白烧 token。这篇把我踩过的坑、最终留下的 7 个 Skill、以及 manifest.json 里最容易写错的字段全部摊开讲。
这篇适合谁
- 已经在用 Codex Agent(或 Codex CLI)写代码,想搞清楚 Skills 配置格式的开发者
- 装了一堆 Skill 但发现账单异常,想知道哪些配置会导致额外 token 消耗的人
- 从 Cursor / Cline 转过来,对 Codex 的 config 体系还不熟的同学
- 想用 o3 / o4-mini 跑 Agent 但怕成本失控的独立开发者
整体流程
- 安装并初始化 Codex CLI
- 理解 Skill 配置文件的四个核心字段
- 在
.codex/config.yaml中注册 Skills - 验证 schema 是否合法(别等运行时才报错)
- 识别哪些配置会触发"隐性 token 消耗"
- 根据场景精简到真正有用的 Skill 集合
graph TD
A[编写 Skill JSON] --> B[注册到 config.yaml]
B --> C[验证 schema 合法性]
C --> D{schema 合法?}
D -->|是| E[Agent 运行时按 description 决策调用]
D -->|否| F[报错 / 静默降级]
E --> G[检查 token 消耗是否合理]
先说结论
| Skill 名 | 留/删 | 原因 |
|---|---|---|
| web_search | ✅ 留 | 高频刚需,description 写精确后误触率 <5% |
| file_tree | ✅ 留 | 零参数,几乎不消耗额外 token |
| run_tests | ✅ 留 | 后端必备,handler 本地执行不走 API |
| git_diff | ✅ 留 | code review 场景核心 |
| db_query | ✅ 留 | 但要加 enum 限制表名,否则 Agent 会瞎猜 |
| lint_fix | ✅ 留 | 配合 eslint/ruff,handler 本地跑 |
| deploy_preview | ✅ 留 | 前端项目部署预览一键触发 |
| translate_text | ❌ 删 | description 太泛,Agent 把所有含外语的上下文都往里塞 |
| summarize_url | ❌ 删 | 每次调用先抓网页再总结,单次 2000+ token |
| code_explain | ❌ 删 | 和 Agent 本身能力重叠,纯浪费 |
前置步骤:安装 Codex CLI
如果你从 Cursor / Cline 转过来,Codex CLI 需要单独安装。官方安装方式:
npm install -g @openai/codex
安装完成后初始化项目配置:
cd your-project
codex init
这会在项目根目录生成 .codex/ 文件夹和初始 config.yaml。后续所有 Skill 配置都基于这个目录结构。具体安装要求和版本信息以 OpenAI 官方文档 为准。
第一步:理解 Skill 配置的四个核心字段
一个最小可用的 Skill 长这样:
{
"name": "web_search",
"description": "Search the web when user asks about events after 2024.",
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]},
"handler": "./handlers/web_search.py"
}
四个字段缺一个都会炸。name 不填直接报错:
ValidationError: Skill 'name' field is required and must be a non-empty string
这个还好,至少报出来了。真正坑的是下面两个。
第二步:parameters 字段——符合 JSON Schema 规范,写错静默降级
parameters 必须是符合 JSON Schema 规范的对象(具体支持的特性以官方文档为准,并非所有 Draft 版本特性都被完整支持)。很多人(包括我)一开始写成这样:
"parameters": {"query": "string"}
跑起来不报错,但 Agent 调用时参数校验直接跳过,传什么进来都不拦。等你发现 query 收到了一个 number 类型,已经是运行时了。以下是示意性报错(实际报错格式以运行时输出为准):
Error: Skill schema mismatch - expected type 'string' but received 'number' for parameter 'query'
正确写法必须有顶层 "type": "object" 和 properties:
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]
}
我现在写完 Skill 配置都会跑一遍本地校验:
import jsonschema
schema = {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}
jsonschema.validate({"query": "test"}, schema)
print("Schema valid ✓")
两秒钟的事,省得后面排查半天。
第三步:handler 路径——相对路径的基准目录不是你想的那个
handler 字段填相对路径时,基准是项目根目录(和 .codex/ 文件夹同级),不是 config 文件所在目录。我第一次配的时候写成 "handler": "../handlers/search.py",以为相对于 .codex/config.yaml,结果报错(示意):
Error: Cannot find skill handler at path './handlers/search.py': ENOENT: no such file or directory
改成 "handler": "./handlers/search.py" 就好了。这个报错信息还算友好,但如果你一次注册 10 个 Skill,它只报第一个失败的,剩下的静默跳过——你以为都注册成功了,其实只有一半在工作。
改完 config 后建议跑一次完整的加载验证,确认所有 Skill 都正确注册。具体调试命令以你所用版本的 CLI 文档为准。
第四步:description 写太泛 = 账单翻倍(这是我最大的坑)
这才是重点。Codex Agent 决定"要不要调用某个 Skill"完全靠 description 字段——它会把所有已注册 Skill 的 name + description 拼进 system prompt,让模型自己决策。
问题来了:每轮对话,所有 Skill 的 description 都会被塞进上下文。
粗略估算一下:34 个 Skill,每个 description 平均 80 字符,纯文本部分约 2720 字符,按 4 字符/token 估算约 680 token。加上 name 字段、JSON 结构开销、以及 system prompt 的其他固定内容,实际输入 token 消耗会更高,但具体数字取决于你的 description 实际长度和模型的 tokenizer,建议用 OpenAI Tokenizer 自行测量。
更狠的是,如果 description 写得模糊(比如 "Translate text between languages"),Agent 会在任何出现非英语内容的上下文里触发调用——即使用户根本没让它翻译。每次触发又是一轮 function call 的 token 往返。
官方建议 description 控制在 50-150 字符,1-3 句话,明确写出触发条件。
反面教材:
"description": "Summarize content from URLs"
正面教材:
"description": "Fetch and summarize a URL. Only use when user explicitly provides a URL and asks for a summary."
加了 "Only use when" 这个限定后,我的 summarize_url 误触率从每天 40+ 次降到了 3 次。但最后我还是删了它,因为每次调用要先抓网页内容再走一轮 LLM 总结,单次消耗 2000-3000 token。
第五步:在 config.yaml 中批量注册
.codex/config.yaml 放在项目根目录下的 .codex/ 文件夹,和 .git 同级:
skills:
- ./skills/web_search.json
- ./skills/file_tree.json
- ./skills/run_tests.json
- ./skills/git_diff.json
- ./skills/db_query.json
如果 config.yaml 本身格式写错(比如缩进不对、冒号后缺空格),会直接解析失败:
ConfigError: config.yaml parse error at line 12: unexpected token
看到 parse error 就去检查 YAML 格式,常见问题是缩进用了 tab 而不是空格,或者列表项前缺少 -。
我最终留下的 7 个 Skill 配置要点
简单列一下每个的 description 写法核心:
web_search:限定"用户问 2024 年之后的事件时才触发"
file_tree:零参数 Skill,parameters 写空对象 {"type": "object", "properties": {}},几乎不消耗额外 token
run_tests:handler 指向本地 pytest/jest,不走 API,成本为零
git_diff:限定"用户提到 diff/review/变更时触发"
db_query:parameters 里用 enum 限制可查询的表名,防止 Agent 瞎猜表结构
"properties": {
"table": {"type": "string", "enum": ["users", "orders", "products"]},
"query": {"type": "string"}
}
lint_fix:handler 本地跑 ruff/eslint,不消耗 API token
deploy_preview:限定"用户明确说部署/预览时才触发"
不同场景怎么选
后端为主的项目:留 run_tests + git_diff + db_query,其他按需。测试和 lint 类 Skill 的 handler 跑本地命令,成本几乎为零。
前端项目:留 file_tree + lint_fix + deploy_preview。前端文件多且碎,file_tree 能帮 Agent 快速定位组件位置。
个人开发者控成本:Skill 总数控制在 5 个以内。每多一个 Skill 的 description 就多一点固定的输入 token 开销。预算紧张的话,config 里把模型切到 o4-mini(输入约 $1.10/M token,以官方最新定价为准),牺牲一点推理能力换成本。
团队协作:把 Skill 配置文件放进 git 仓库统一管理。如果你们使用 OpenRouter 等 API 聚合网关,通常可以按 API Key 维度查看各成员的 token 消耗明细,月底对账时能直接定位到是哪个 Skill 在烧钱。具体接入方式参考所用网关的官方文档,一般只需在初始化时修改 base_url:
client = openai.OpenAI(
api_key="your-key",
base_url="https://your-gateway.example.com/v1" # 替换为实际可用的网关地址
)
踩坑记录 / 常见问题 FAQ
Q: Codex Skills 和 OpenAI Function Calling 有什么区别?
Skills 是更高层的抽象,内置了执行环境和代理调度逻辑。Function Calling 是底层 API 机制,Skills 底层基于它实现,但多了 handler 执行、schema 校验、调度决策这些层。可以理解为 Skills = Function Calling + 执行引擎 + 触发决策。
Q: .codex/config.yaml 放在哪个目录?
项目根目录下的 .codex/ 文件夹,和 .git 同级。不是 home 目录,不是 ~/.config/,就是项目根。
Q: Skill 的 description 写多长合适?
官方建议 50-150 字符,1-3 句话。关键是写清楚触发条件("Only use when..."),不然 Agent 会在不该调用的时候调用,白烧 token。
Q: 怎么调试 Skill handler 执行失败?
查看 CLI 的详细日志输出(具体参数以你所用版本文档为准)。Python handler 支持 async def,Node.js handler 可以返回 Promise。handler 里有 stderr 输出也会被捕获到日志里。
Q: 注册了很多 Skill 但感觉 Agent 从来不调用某个,怎么回事?
大概率是 description 和当前对话上下文匹配度太低,模型判断不需要触发。也可能是 schema 格式有问题导致静默降级——通过详细日志确认一下是否真的加载成功了。
Q: 能不能限制某个 Skill 的调用频率?
目前原生不支持 rate limit 配置。我的做法是在 handler 里自己加计数器,超过阈值直接返回空结果。不优雅但管用。
小结
折腾了大半个月,结论就是:Skill 不是越多越好,5-7 个精准配置的比 30 个模糊的强太多。核心三点——description 写明触发条件、parameters 用严格的 JSON Schema 格式、handler 能跑本地的就别走 API。成本敏感的话,先把 Skill 总数砍到个位数,账单下降会很明显。
更多推荐


所有评论(0)