写了 266 行 SKILL.md,Codex 死活不认——揪出 UTF-8 BOM 这个隐形杀手


摘要:精心写了一个 Codex Skill,文件存在、内容正确、路径没问题——但 Codex 就是找不到它。排查发现全仓 20 个 skill 中,19 个文件开头是 ---,只有我一个开头是 EF BB BF。本文还原完整的排查过程,讲清楚 BOM 是什么、为什么让 skill 静默失效、以及如何一劳永逸地避免。


📌 前言 / 问题场景

团队规范要求所有 commit message 遵循 Conventional Commits 格式。每次 git commit 都要手写 feat:fix:BREAKING CHANGE:,烦得不行。

于是你写了 200 多行 SKILL.md,做了一个 $git-commit skill——输一段改动描述,自动生成规范的 commit message。写好、测试、放到 ~/.codex/skills/git-commit/,重启 Codex。

敲下 $git-commit "修了登录页token过期不刷新的bug"

Codex 回你一句「未找到该 skill」。你以为是触发词没写对,又把 description 里的关键词改了三版——gitcommit提交Conventional Commits 全塞进去。

依旧不认。

这就很邪门了。文件存在、路径正确、YAML 没报错——凭什么不认?


📋 环境说明

组件 版本/说明
Codex CLI open-source 版本
OS Windows 11
Skill 路径 ~/.codex/skills/git-commit/SKILL.md
文件编码 UTF-8(带 BOM)← 就是它
参考对比 同目录下 19 个正常工作的 skill

🔍 问题复现

  1. 写好 SKILL.md,放到 skills 目录
  2. 重启 Codex
  3. 输入 skill 名或触发词

预期:Codex 加载 skill,匹配触发词,开始执行。

实际:完全静默。不报错、不警告、不做任何提示——skill 被当空气。


🧭 排查过程

尝试 1:检查文件是否存在 ❌

第一反应——路径写错了?

Test-Path "~/.codex/skills/git-commit/SKILL.md"
# True

文件在,266 行,内容完整。排除。

尝试 2:怀疑 YAML 格式有问题 ❌

会不会是 description 字段的引号没转义对?翻出正常工作的 java-study-notes 对比:

正常 skill:  description: Java beginner study assistant...
我的 skill:  description: Write viral CSDN technical articles...

格式完全一致。而且 YAML 如果有语法错误,通常会有报错——这里连报错都没有。

尝试 3:怀疑 description 关键词匹配不上 ❌

注意到 description 里有 \"写文章\" 这种写法——PowerShell 写文件时把双引号转义了。把 \" 全部替换成 "

重启 → 还是不认。

尝试 4:对比字节级差异 ✅

前面三步都排除了,只能往更底层挖——直接比文件原始字节:

$b = [System.IO.File]::ReadAllBytes("SKILL.md")
Write-Host $b[0].ToString('X2') $b[1].ToString('X2') $b[2].ToString('X2')
# 输出: EF BB BF

正常 skill 的前 3 个字节呢?

# java-study-notes:
# 输出: 2D 2D 2D  (= "---")

破案了。

全仓 20 个 skill,遍历检查:

False | 2D 2D 2D | algorithmic-art
False | 2D 2D 2D | brand-guidelines
False | 2D 2D 2D | canvas-design
...(中间 16 个全是 2D 2D 2D)...
True  | EF BB BF | csdn-viral-article   ← 就这一个!

19 个正常 skill 开头都是 ---。只有我这一个开头是 EF BB BF——UTF-8 BOM。


🛠️ 解决方案

去掉 BOM,重塑文件:

# 读取内容
$content = [System.IO.File]::ReadAllText("SKILL.md")

# 用不带 BOM 的 UTF-8 重新写入
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText("SKILL.md", $content, $utf8NoBom)

# 验证
$b = [System.IO.File]::ReadAllBytes("SKILL.md")
# 前 3 字节必须是 2D 2D 2D

覆盖后重启 Codex——$csdn-viral-article 秒唤醒。


🧠 原理分析

BOM 到底是什么?

BOM(Byte Order Mark)是 UTF-8 文件开头的一个可选标记,固定值 EF BB BF,占 3 个字节。

它的原始用途是告诉文本编辑器:「我是 UTF-8 编码的,别用 GBK 猜」。但 UTF-8 本身没有字节序问题,所以 BOM 对 UTF-8 来说是多余的——大部分现代工具都不需要它。

为什么 PowerShell 会加 BOM?

Set-Content -Path "file.md" -Encoding UTF8

PowerShell 的 -Encoding UTF8 默认带 BOM。这不是 bug,是微软的设计选择——Windows 生态里有些老工具(比如记事本)靠 BOM 判断编码。想不带 BOM,得显式用:

# PowerShell 5.1+
Set-Content -Path "file.md" -Encoding UTF8NoBOM

# 或者直接用 .NET
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText("file.md", $content, $utf8NoBom)

为什么 Codex 不认 BOM?

Codex 的 YAML 解析器读取 SKILL.md 时,期望第一个字符就是 -(YAML frontmatter 的 --- 开头)。

期望:     |---\nname: ...|
实际读到: |[EF BB BF]---\nname: ...|

解析器看到开头的三个陌生字节,判断「这不是一个合法的 YAML 文件」——然后静默跳过。不报错,因为它在设计上允许目录里有非 skill 文件(README、脚本等),跳过不认识的文件是正常行为。

为什么这是「静默」的?

这是最坑的地方:

  • 如果 skill 不存在 → 静默(正常,可能根本没装)
  • 如果 skill 格式错误 → 静默(Codex 把它当普通文件跳过)
  • 如果 skill 完全正确,只是多了 BOM → 同样静默

三种情况,同一种表现。没有任何线索告诉你问题出在哪。


📝 总结

  1. BOM 不是 bug,是 PowerShell 默认行为——Set-Content -Encoding UTF8 会加、UTF8NoBOM 不加
  2. Codex skill 静默失效的最难排查原因之一——没有报错、没有日志、没有任何提示
  3. 排查方法:直接读文件前 3 个字节,必须是 2D 2D 2D---),不能是 EF BB BF
  4. 修复方法:用 UTF8Encoding($false) 重新写入,一劳永逸
  5. 通用教训:任何 YAML-based 的配置系统(GitHub Actions、K8s、Ansible),如果文件「看起来对但就是不生效」,查一下 BOM——它比你想的更常见

📚 参考资料


© 本文为原创内容,转载请注明出处。

如果这篇文章帮你避开了 BOM 的坑,欢迎点赞 👍、收藏 ⭐、关注 ➕,后面还会分享更多 Codex Skill 开发中的暗坑记录。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐