谁要是没在 Windows 上被中文乱码折磨过几回,那基本不算正经写过文档。上周我用 codex 在命令行里生成一份 Markdown 技术方案,打开文件的那一刻差点崩溃——标题是"锟斤拷",正文是"烫烫烫",表格里全是问号,整篇文档跟被加密了一样。我第一反应是重新生成,但结果依然如故。问题不在 codex 的生成质量,而在 Windows 下文件编码和终端代码页(Code Page)之间的错位。这篇文章就围绕这个场景,把"Windows + codex + 中文乱码"这条链路拆开揉碎,然后给你一个能落地的 skill 方案,让 codex 在 Windows 下写中文文档时自动规避乱码。适合所有在 Windows 上用 codex、Claude Code 或类似 AI 编程助手写中文文档、代码注释、Markdown 的开发者,也适合刚接触 skill 机制、想知道它到底怎么定义规则、怎么让 AI 稳定执行编码约定的人。
1. 乱码问题出在哪一环:codex 在 Windows 下写中文文档的全链路
1.1 那个满屏"锟斤拷"的下午
先说结论:codex 本身生成内容的能力没有毛病,中文文档内容是对的,但在 Windows 环境下,内容从"生成"到"落盘"再到"被打开",中间隔着好几层编码转换,任何一层对不上,你看到的就全是乱码。
我当时的操作很简单:codex进入对话模式,让它写一篇《项目部署说明.md》,内容包含了标题、段落、表格、代码块。codex 在终端里正常输出,我看着也没问题,但当我在 Notepad++ 和 VSCode 里打开这个文件时,中文字符全部变成了"锟斤拷"和方块。更诡异的是,同一个文件用系统自带的记事本打开又正常。
这就指向了一个经典场景:文件实际编码是 UTF-8,但某些编辑器按 GBK 解码;或者文件被保存成了 GBK,而 codex 和 VSCode 默认按 UTF-8 处理。Windows 中文系统的默认活动代码页通常是 936(GBK),而 AI 工具生成的文本几乎都是 UTF-8。两边不对表,乱码就来了。
1.2 三条链路,三种乱码
在 Windows 下用 codex 写中文文档,乱码其实分三条链路,症状不同,根因也不同。我建议先分清你到底踩了哪条链路的坑,再动手解决。
| 链路 | 发生位置 | 典型症状 | 根因 |
|---|---|---|---|
| 终端输出链路 | codex 在 cmd / PowerShell 里直接打印中文 | 中文变成"鈥樷�"、问号 | 终端代码页与输出编码不一致 |
| 文件生成链路 | codex 生成的 .md / .txt / .html 文件 | 打开后标题正文全乱 | 文件写入编码与编辑器解码编码不一致 |
| 代码运行链路 | codex 生成的脚本在 Windows 控制台运行 | print("中文") 输出乱码 | 脚本编码/运行时标准输出编码不匹配 |
注意第三种链路最常见也最隐蔽:codex 生成的 Python 脚本里明明写着print("部署完成"),但你一运行,控制台输出却是鍙戦儕瀹屾垚。这不是脚本内容坏了,而是 Python 在 Windows 控制台以 GBK 解码了 UTF-8 源文件里的字符串,或者输出时被控制台重新编码了一遍。
1.3 定位问题的一句话方法论
我的经验是,遇到乱码先别急着改文件,先回答一个问题:这个乱码是"文件本身坏了",还是"文件没坏,显示它的程序搞错了编码"?判断方法很简单:把同一个文件拖到 Chrome 浏览器里打开,或者用 VSCode 右下角手动切换"重新打开以编码"为 UTF-8,如果文字恢复,说明文件是好的,只是打开姿势不对;如果还是乱,说明文件内容在写入时就已经被错误编码了。这个判断决定了后面你是该改编辑器设置,还是该改 codex 的生成规则——而后者正是 skill 最擅长管的。
2. 编码三件事:UTF-8、GBK、BOM 怎么影响你看到的文字
2.1 UTF-8 和 GBK 到底差在哪
要说清楚乱码,绕不开 UTF-8 和 GBK 这两个词。GBK 是 Windows 中文版历史上最常用的编码,一个汉字占两个字节,向下兼容 GB2312,中文系统里的记事本默认读写很多场景都围绕 GBK 展开。UTF-8 是国际化编码,一个汉字通常占三个字节,能覆盖全球所有字符,也是 AI 工具、Git、Linux、macOS 和现代 Web 的默认选择。
关键在于:同一段中文,用 UTF-8 编码和用 GBK 编码,得到的字节序列完全不同。你把 UTF-8 的字节序列交给一个按 GBK 解码的程序,它就会把这串字节拆成 GBK 的字符组合,结果就是一堆完全不相干的中文生僻字和符号——这就是"锟斤拷"的来源。
"锟斤拷"这三个字大有来头:UTF-8 里有一个常见替换字符 U+FFFD(�),它的 UTF-8 编码是EF BF BD。这段字节被 GBK 解码时,EF对应"锟",BF对应"斤",BD对应"拷",于是连续多个替换字符就变成了连续多个"锟斤拷"。所以当你看到满屏"锟斤拷"时,几乎可以断定:原始内容经过了一次错误的 GBK 解码,或者以 GBK 方式读取了 UTF-8 数据,且中间经过了 U+FFFD 替换。
2.2 BOM 为什么在 Windows 下不能随便删
BOM(Byte Order Mark)是写在文件开头的特殊字符,用来标记编码格式。UTF-8 的 BOM 是EF BB BF,Windows 记事本看到这个字节序列就能确定"这是 UTF-8 文件"。VSCode、Notepad++ 也能通过 BOM 自动识别编码。
很多 Linux 和 macOS 开发者讨厌 BOM,因为它在文件头部多了三个无意义字节,可能干扰 shell 脚本执行、Node.js 模块加载。但 Windows 下情况不同:没有 BOM 的 UTF-8 文件,老版本记事本会默认按 ANSI(即 GBK)解码,中文必乱。所以我的建议很直接:在 Windows 下用 codex 生成中文文档,优先保存为 UTF-8 with BOM(Python 里叫utf-8-sig,VSCode 里叫UTF-8 with BOM)。当你打开 Markdown、TXT、HTML 这类文档时,BOM 就是给 Windows 编辑器认亲的那张身份证。
2.3 chcp 65001 与"使用 Unicode UTF-8 提供全球语言支持"的关系
除了文件编码,终端代码页也得管。Windows 命令行默认活动代码页是 936,也就是 GBK。想让终端正确显示和输出 UTF-8 内容,可以在 cmd 里执行:
chcp 65001这会把当前控制台的活动代码页切换为 UTF-8。在 PowerShell 里,除了chcp 65001,最好再设置一下控制台的输出编码:
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8第一条命令让控制台解码外部程序输出时用 UTF-8,第二条让 PowerShell 管道输出时用 UTF-8。两条一起设置,能解决九成终端输出乱码问题。
如果你想一劳永逸,还可以去 Windows 设置里开启"使用 Unicode UTF-8 提供全球语言支持":设置 -> 时间和语言 -> 语言和区域 -> 管理语言设置 -> 更改系统区域设置 -> 勾选 Beta 版选项。开启后全系统默认代码页变成 65001,乱码会大幅减少,但一些老软件可能会出现新的乱码或字体问题,这个取舍要看你的具体环境,不必强行全局开启。
3. 设计"乱码终结者"skill:目录、规则、脚本一把梭
3.1 skill 的存放位置和加载机制
你也许听说过 skill,它是给 AI 助手定义"行为准则"的一种机制:你写一份规则文档,AI 在生成内容时按这份规则执行。codex 支持在用户目录或项目目录下放置 skills,每个 skill 是一个文件夹,里面有一个SKILL.md,这个文件就是全部规则的载体。
在 Windows 下,skill 的典型路径是:
C:\Users\<你的用户名>\.codex\skills\chinese-doc-encoding\SKILL.md如果你想让某个项目单独使用这个 skill,也可以放到项目目录下:
<项目目录>\.codex\skills\chinese-doc-encoding\SKILL.mdskill 文件夹的名字建议用英文小写加连字符,文件夹内只放SKILL.md和配套脚本。加载时,codex 会根据请求内容匹配 skill 的描述,一旦匹配上,就把SKILL.md里的规则当作系统约束来执行。
3.2 SKILL.md 逐段拆解:给 codex 立规矩
SKILL.md开头的一段 YAML frontmatter 用于声明技能名称和触发条件,然后是正文规则。我这里给出一份可直接抄的SKILL.md:
--- name: chinese-doc-encoding description: 在 Windows 环境下编写中文文档、代码注释、脚本内容时,统一使用 UTF-8 编码策略,避免中文乱码。适用于生成 Markdown、TXT、HTML、Python、JS 等文件。 --- # 中文文档编码规范 当用户环境是 Windows 时,你必须遵循以下规则: 1. 生成任何包含中文的文档(Markdown、TXT、HTML、LaTeX 等),一律按 UTF-8 编码保存;若目标文件会被记事本、旧版编辑器打开,则保存为 UTF-8 with BOM(utf-8-sig)。 2. 生成 HTML 文件时,在 <head> 中显式声明 <meta charset="utf-8">,不要省略。 3. 生成 Markdown 文件时,文件头不强行要求添加编码声明,但要保证文件内容本身是以 UTF-8 写的;不要在 Markdown 代码块中插入编码无关字符。 4. 生成 Python 脚本时,若脚本包含中文字符串字面量,应给出运行提示:用户可先执行 chcp 65001,或在脚本中通过 sys.stdout.reconfigure(encoding='utf-8') 处理输出。 5. 生成 JS/TS 脚本时,若命令行环境为 Windows cmd,建议脚本内避免直接打印非 ASCII 字符,或提示用户切换代码页。 6. 用户要求修复已有乱码文件时,调用配套 fix_encoding.py 脚本处理,不要直接手改二进制。 7. 向用户提供任何"乱码或编码问题"的建议时,先让用户执行 chcp 查看当前活动代码页,再根据输出决定方案。 8. 不要建议用户删掉 UTF-8 BOM,除非明确知道读取方是 Linux 工具链。 # 可用工具 - fix_encoding.py:自动检测文件编码并转换为指定编码。这份规则要解决的核心问题其实就八个字:生成时定编码,修复时走脚本。你写文档,codex 生成 UTF-8;你丢给它乱码文件,它调用脚本修复;你在命令行跑脚本出现乱码,它知道先让你查代码页。规则清晰,codex 的发挥才不会飘移。
3.3 配套修复脚本 fix_encoding.py:乱码文件的"后悔药"
光有规则还不够,你最好给 codex 一把实际能用的"手术刀"。我在 skill 目录里放了一个fix_encoding.py,用来检测文件编码并重新保存。脚本逻辑很简单:尝试用 UTF-8 解码,失败就尝试 GBK 解码,之后按你指定的目标编码重新写回。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """检测并修复 Windows 常见中文乱码文件。""" import sys from pathlib import Path def detect_and_decode(data: bytes): # 优先按 UTF-8 解码 try: return data.decode('utf-8') except UnicodeDecodeError: pass # 再按 GBK 解码 try: return data.decode('gbk') except UnicodeDecodeError: return None def main(): if len(sys.argv) < 2: print("用法: python fix_encoding.py <文件路径> [--to utf-8-bom|utf-8|gbk]") return target = Path(sys.argv[1]) if not target.exists(): print(f"文件不存在: {target}") return raw = target.read_bytes() text = detect_and_decode(raw) if text is None: print("无法自动识别原始编码,请用编辑器手动处理。") return mode = sys.argv[2] if len(sys.argv) > 2 else "--to utf-8-bom" if mode == "--to gbk": target.write_text(text, encoding='gbk') print("已转换为 GBK:", target) elif mode == "--to utf-8": target.write_text(text, encoding='utf-8') print("已转换为 UTF-8(无BOM):", target) else: target.write_text(text, encoding='utf-8-sig') print("已转换为 UTF-8 with BOM:", target) if __name__ == '__main__': main()这个脚本不是万能的。它适用于"文件内容仍然是完整的 UTF-8 或 GBK 字节,只是被错误解码过或被多个编辑器反复保存"的场景。如果乱码内容在保存过程中已经被替换字符(U+FFFD)破坏了原始信息,比如那串"锟斤拷"已经写进了文件,那么解码再转码也无法还原,那时候只能从源头重新生成。我加粗提醒一句:修复脚本是后悔药,不是时光机。
3.4 低版本 / 特殊环境下用 AGENTS.md 平替
如果你用的是某个还没支持 skills 机制的 codex 版本,或者团队协作时希望规则跟着项目走而不依赖个人目录,那可以用AGENTS.md平替。在项目根目录放一个AGENTS.md,把上面SKILL.md里的规则抄进去就行。codex 读取项目指令时会自动加载AGENTS.md,效果等同于一个常驻 skill。区别在于AGENTS.md是项目级的,跟着仓库走,每个人 clone 下来都生效;skill 是用户级的,只管你自己。
我自己现在的习惯是:个人全局环境放 skill,重要项目里再放一份精简版AGENTS.md。双保险的好处是,即使某天 codex 升级后 skill 加载逻辑变了,项目级指令依然兜底。
4. 实测记录:装上 skill 后 codex 的输出变化
4.1 怎么确认 skill 被 codex 正常加载
每次装完 skill,最怕的就是根本没被加载,你还以为规则生效了。确认方法有两个。第一,在对话里直接问 codex:"你目前是否了解 chinese-doc-encoding 这个技能?"如果它回答出了规则里的关键条目,说明加载成功。第二,故意让它生成一个只需要"带 BOM"判断的场景,比如让它写一段 Python 代码并说明编码处理方式,如果它在我没提示的情况下主动提到utf-8-sig或chcp 65001,说明 skill 已经进入它的行为约束。
4.2 测试一:生成一篇带中文标题、代码块、表格的 Markdown
我让 codex 写一篇《Windows 下 Docker 部署注意事项.md》,并特意要求包含表格和代码块。生成完成后,我在 VSCode 里打开,中文显示全部正常;用系统记事本打开,也正常——这正是带 BOM 的 UTF-8 文件的优势。VSCode 右下角显示UTF-8 with BOM,一目了然。之后我又用 Git 提交了这个文件,命令行里git diff时中文没有乱码,这一点也值得注意:Git 对带 BOM 的 UTF-8 文件处理很稳定。
再看终端链路。我在 cmd 里用type命令直接查看这个文件,因为之前执行过chcp 65001,输出正常;我又在没切代码页的新终端试了一次,中文变成了"鈥"? 这就是终端链路还没解决时的标准症状。所以记得,skill 让文件生成对了,但终端能否正确显示还得靠代码页。
4.3 测试二:生成含中文字符串的 Python 脚本并运行
第二个测试更接近日常:让 codex 写一个deploy_check.py,里面有几行print("部署成功")之类的中文输出。加了 skill 之后,codex 在脚本开头自动给出了运行建议:在终端先执行chcp 65001。我照做后运行,输出正常;不照做直接跑,乱码又出现了。
顺便提一个不错的做法:如果脚本是你自己会长期用的,可以在 Python 代码里直接固定输出编码:
import sys if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8': sys.stdout.reconfigure(encoding='utf-8')这样只要终端代码页是 65001,输出就能稳定不乱。codex 在 skill 规则约束下会自动生成这类代码,节省了我手动告诉它的时间。
4.4 测试三:对已有乱码文件执行修复
最后一个测试是对之前那份乱码文档执行修复。我拿了一个已经变成"锟斤拷"的旧文件,运行:
python fix_encoding.py 项目部署说明.md --to utf-8-bom脚本识别出文件原始内容是 UTF-8 字节被 GBK 解码后重新保存的情况,于是先按 GBK 解码文本,再以utf-8-sig编码写回。完成后重新打开,内容恢复。不过要提醒的是,这个场景能修复的前提是文件内容没有被替换字符完全破坏;如果你在编辑器里手动保存过一次,导致 U+FFFD 真的写入文件,那脚本也救不回来。所以我现在的习惯是:发现乱码后立刻停手,不要反复用不同编辑器打开保存,直接交给脚本处理。
5. 装完 skill 依旧乱码?按这条链路逐级排查
5.1 先分清"文件乱"和"显示乱"
装了 skill 还乱,大概率不是 skill 没生效,而是你把问题类型判断错了。我这里总结一个字少事大的排查顺序,按照这个顺序来,基本十分钟定位:
- 用 VSCode 打开文件,看右下角显示的编码是什么。
- 执行"重新打开以编码",分别尝试
UTF-8和GBK,看哪种恢复正常。 - 如果某一种编码下完全正常,说明文件内容是完好的,只是默认编码选错。此时不要动文件内容,修改编辑器或系统的默认编码策略即可。
- 如果两种编码都乱,那要考虑内容是否真的损坏,进入第 5.2 节。
5.2 编辑器默认编码与"自动检测"的坑
VSCode 默认files.encoding是 UTF-8,这对大多数场景是好事,但对从旧环境拿来的 GBK 文档反而不友好。如果你经常和 GBK 文件打交道,可以在用户设置里加上:
"files.encoding": "utf8", "files.autoGuessEncoding": trueautoGuessEncoding开启后,VSCode 会尝试自动猜测文件编码,GBK 中文文档能正常显示的概率大大提高。但注意,自动猜测不是万无一失,如果文件字节序列比较短(比如只有一行中文),猜测可能出错,这时候手动选择编码是最可靠的。
5.3 终端代码页、输出编码和重定向
排查命令行场景时,记住一个固定动作:先执行chcp看看当前代码页。如果输出是Active code page: 65001,那问题大概率不在终端,而在程序的输出编码;如果是936,那就先执行chcp 65001再试。
还要注意一个隐蔽场景:重定向输出时乱码,但屏幕上正常。命令codex generate > output.log这种用法会把程序输出重定向到文件,而此时终端的代码页可能不再对输出生效,程序会按系统默认 ANSI 编码写入文件,最后你在 VSCode 里打开 log 就乱。解决方式是让程序自己声明输出编码,或者在命令前设置环境变量。例如在 PowerShell 里:
$env:PYTHONIOENCODING = "utf-8" python script.py > output.log这个坑特别容易和"文件生成链路"混淆。我踩过一次之后,现在凡是要重定向中文输出,都会先想清楚"写入端编码"是什么,而不是只看屏幕显示。
5.4 接口请求异常导致的"假乱码"(那些长得像乱码的报错信息)
最后一种"乱码"其实不是编码问题,而是信息错乱。有时候 codex 请求服务时返回异常,终端会吐出一大段调试信息,里面夹杂着codex endpoint /responses、failed while handling之类的字样,并带着一串长长的错误对象和转义字符。这段内容在终端里因为颜色转义和缩进问题,中文字符会挤在一起,看起来就像乱码,但本质是请求异常。
遇到这种情况,别急着改编码。先看请求是否成功、返回的 HTTP 状态码是什么、报错关键字是什么。如果是接口服务或本地依赖服务没启动导致的异常,中文文档写得再规范也没用,因为问题发生在内容生成之前。我的一般顺序是:先处理掉这类请求层报错,再回头检查文档编码。不要把两类问题混在一起排查,否则你会在错误的方向上浪费两小时。
关于这个 skill 的后续扩展想法
刚开始我只把chinese-doc-encoding当成防乱码工具,用了一段时间发现它还能做更多事,比如在生成文档时自动附加"编码说明"段落,让团队里不熟悉编码的同事也知道这个文件为什么带 BOM、执行前为什么先切代码页。你也可以把fix_encoding.py扩展成批量扫描整个目录,对文件名里带中文的 Markdown 文件提前做编码体检,把潜在乱码扼杀在提交之前。我的体会是:编码这种事,靠人记不如靠规则管,把这个场景固化成一个 skill,codex 每次生成文档时都自动把关,Windows 下的中文文档工作流才算真正舒坦。