Codex Skill 因 UTF-8 BOM 无法加载的排查与修复

记录 Codex skill 明明存在却不显示时,如何检查 SKILL.md 文件头是否带 UTF-8 BOM,并用 PowerShell 转换为无 BOM UTF-8。

TutorialCodexSkillsUTF-8 BOMPowerShell

现象

Codex skill 已经放在 skills 目录里,例如:

C:\Users\<username>\.codex\skills\<skill-name>\SKILL.md

目录结构、SKILL.md、YAML frontmatter 看起来都正常,但重启 Codex 之后,这些 skill 仍然没有出现在当前会话的 skill 列表中。

原因

Codex 识别 skill 时,会先读取每个 skill 目录下的 SKILL.md,并要求文件开头是 YAML frontmatter 分隔符:

---
name: your-skill-name
description: ...
---

正常情况下,文件头的前三个字节应该是:

2D-2D-2D

如果文件保存成了带 BOM 的 UTF-8,真实字节头会变成:

EF-BB-BF-2D-2D-2D

其中 EF-BB-BF 是 UTF-8 BOM。肉眼看文件仍然是从 --- 开始,但 loader 看到的第一个字节不是 -,就可能误判为缺少 YAML frontmatter。

参考:Codex Skill 明明在目录里,为什么就是不显示?

检查单个文件头

在 PowerShell 中检查某个 SKILL.md 的前 6 个字节:

$path = 'C:\Users\<username>\.codex\skills\<skill-name>\SKILL.md'
$bytes = [System.IO.File]::ReadAllBytes($path)
($bytes[0..5] | ForEach-Object { $_.ToString('X2') }) -join '-'

如果输出是:

EF-BB-BF-2D-2D-2D

说明文件带 UTF-8 BOM。

如果输出是:

2D-2D-2D-0D-0A-6E

或者前三个字节是 2D-2D-2D,说明文件直接以 --- 开头,符合 Codex loader 对 frontmatter 的预期。

批量扫描 Skills 目录

可以批量扫描一个 skills 根目录下所有 SKILL.md

$root = 'C:\Users\<username>\.codex\skills'

Get-ChildItem -Path $root -Directory |
  Where-Object { $_.Name -ne '.system' } |
  Sort-Object Name |
  ForEach-Object {
    $path = Join-Path $_.FullName 'SKILL.md'
    if (Test-Path -LiteralPath $path) {
      $bytes = [System.IO.File]::ReadAllBytes($path)
      $first6 = ($bytes[0..([Math]::Min(5, $bytes.Length - 1))] |
        ForEach-Object { $_.ToString('X2') }) -join '-'

      $hasBom = $bytes.Length -ge 3 -and
        $bytes[0] -eq 0xEF -and
        $bytes[1] -eq 0xBB -and
        $bytes[2] -eq 0xBF

      $startsWithFrontmatter = $bytes.Length -ge 3 -and
        $bytes[0] -eq 0x2D -and
        $bytes[1] -eq 0x2D -and
        $bytes[2] -eq 0x2D

      [PSCustomObject]@{
        Skill = $_.Name
        HasBom = $hasBom
        StartsWithFrontmatter = $startsWithFrontmatter
        FirstBytes = $first6
        Path = $path
      }
    }
  } |
  Format-Table -AutoSize

重点看两个字段:

HasBom 应该是 False

StartsWithFrontmatter 应该是 True

转换为无 BOM UTF-8

如果已经确认某些 SKILL.md 带 BOM,可以用下面的 PowerShell 脚本批量转换。脚本只会处理带 BOM 的文件,其他文件不会重写。

$root = 'C:\Users\<username>\.codex\skills'
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)

Get-ChildItem -Path $root -Directory |
  Where-Object { $_.Name -ne '.system' } |
  ForEach-Object {
    $path = Join-Path $_.FullName 'SKILL.md'
    if (-not (Test-Path -LiteralPath $path)) {
      return
    }

    $bytes = [System.IO.File]::ReadAllBytes($path)
    $hasBom = $bytes.Length -ge 3 -and
      $bytes[0] -eq 0xEF -and
      $bytes[1] -eq 0xBB -and
      $bytes[2] -eq 0xBF

    if ($hasBom) {
      $text = [System.IO.File]::ReadAllText($path, [System.Text.Encoding]::UTF8)
      [System.IO.File]::WriteAllText($path, $text, $utf8NoBom)
      "FIXED $path"
    }
  }