本文由AI创作
事情要从我深夜排查的一个诡异 Bug 说起。
一个接口调用反复失败。报错信息含含糊糊,只甩回来一句"参数解析异常"。
参数文件是我手写的一个 JSON,也就十几个字符。肉眼检查了不下十遍,一个字都没错。换编码,换换行符,重传了五六次——
还是同样的报错。
最后没办法,我把请求体的原始字节 dump 了出来。
文件头赫然躺着三个字节:
EF BB BF。
UTF-8 BOM。
你可能对这个缩写没什么感觉。我换个说法:这是 1990 年代微软记事本遗留下来的"文件头标记",二十多年过去了,绝大多数现代工具都已经抛弃了它。
但它就在我面前。在一个 2026 年创建的、只有一行 { "param1": "test" } 的 JSON 文件里,复活了。
这不是最离谱的部分。
最离谱的是——这个文件,是一个 AI Coding Agent 生成的。
谁干的?逐字节追凶
三个字节的 BOM,到底是谁塞进去的?
嫌疑人就三个:编辑器、AI 的专用写文件工具、Shell 命令。
编辑器第一个排除。现代编辑器保存 JSON,默认都不带 BOM,这事不是它干的。
那就是 AI 的环节出了问题。这个 AI 编程平台提供了一个叫 Write 的专用命令,专门用来创建和修改文件。按理说,用 Write 写出来的文件应该是干干净净的 UTF-8,没有 BOM,没有多余的换行符。
验证一下。用 Write 工具创建几个测试文件,逐字节对比:
text Write 工具输出:7B 20 22 70 61 72 61 6D 31 22 3A 20 22 74 65 73 74 22 20 7D(无 BOM,无 CRLF,纯 UTF-8)
干净。Write 工具确实没问题。
那嫌疑只剩一个:AI 在某个环节没用 Write,而是调了 Shell。
继续验证。把 Windows 上 PowerShell 能写文件的方法全测一遍:
text Set-Content -Encoding UTF8 →EF BB BF ... 0D 0A ← UTF-8 BOM,CRLF 换行Out-File -Encoding UTF8 →EF BB BF ... 0D 0A ← UTF-8 BOM,CRLF 换行> 重定向(默认编码) →FF FE ... 0D 00 0A 00 ← UTF-16LE BOM,每个字符占两字节
全带 BOM。一个不落。
真相大白了。
那个带 BOM 的 JSON 文件,根本就不是 Write 工具创建的。是 AI 在某个步骤,随手敲了一行 PowerShell 命令——Set-Content、或者 Out-File、或者一个 > 重定向——把它写进去的。
三种方法,各有各的坑:前两种给你贴 EF BB BF(UTF-8 BOM),第三种更狠——整个文件编码变成 UTF-16LE,体积直接翻倍。不管哪种,都不是你想要的纯净 UTF-8。
AI 在"写一个文件"这么简单的事上,选了它觉得最顺手的那条路:调 Shell。
而 PowerShell 的默认行为,不是给你干干净净的 UTF-8,是给你各种花式 BOM——要么 UTF-8 带 EF BB BF,要么更离谱的 UTF-16LE 带 FF FE。
复现环境说明 以上测试基于 Windows PowerShell 5.1( 如果你的环境里执行
这会让 |
AI 的七寸:它有专用工具,但它不一定用
BOM 本身不是什么大事,三个字节,修一下几秒钟的事。
但这件事真正让我警铃大作的,不是 BOM——
是 AI 明明有专为这个任务设计的工具,它却选择了不用。
Write 是什么?是这个平台为文件写入专门封装的原子操作。它保证编码干净、不引入副作用、不产生历史包袱。它就是为"创建一个 JSON 配置文件"这个场景而生的。
Shell 命令是什么?一把万能扳手。它能拧螺丝,也能撬钉子、刮墙面、砸核桃。AI 用它来创建文件,不是因为"最合适",而是因为——
"也能行。"
但"也能行"和"最合适"之间那条裂缝,就是 BOM 诞生的地方。
而且这根本不是一个孤例。如果你日常在用 AI 写代码,下面这些场面你一定不陌生:
- AI 用
sed改一行配置,而不是用搜索替换工具,结果少闭合了一个引号,整个文件炸了 - AI 用
cat <<EOF > file写 JSON,转义字符层层嵌套,最后生成的全是\",解析都解析不了 - AI 用
npm install --force解决依赖冲突,而不是花两分钟分析一下到底哪里冲突了
这些场景的底层模式一模一样:
AI 选工具的逻辑,不是"哪个最安全、副作用最小"。是"哪个命令行能跑通"。
为什么会这样?不是笨,是机制问题
你可能会想:现在的 AI 模型这么强,为什么连"创建文件应该用专用工具"这么简单的道理都不懂?
这不是傻。这是工具调用的底层机制决定的。
AI Coding Agent 选工具的过程,本质上是一个概率决策。它的 Prompt 里列了所有可用工具——Write 也在里面,RunCommand 也在里面。当你说"创建一个文件",它要从这堆选项里挑一个。
靠什么挑?
靠模型在训练数据里见过的模式。靠当前的上下文。靠工具描述文本的语义匹配。
在模型的视角里,Set-Content -Path ... -Encoding UTF8 和 Write(file_path, content) 都是"合法的文件创建方式"。没有谁能告诉它"'Write' 更安全,因为不引入 BOM"。
它没有"专用工具优先"这个硬约束,也不具备"Shell 命令可能夹带 BOM"这个隐性知识。
它只是选了一个在概率分布上得分更高的路径。
更麻烦的是多步骤任务。先创建文件、再写入内容、再执行验证——每一步都可能触发新一轮的工具选择。第一轮规规矩矩用了 Write,第二轮手一滑调了 Shell,BOM 就悄悄进来了。
而且一旦文件带上了 BOM,后续 Write 尝试覆写同样内容时,可能会直接跳过——因为内容没变。
BOM 就此扎根。
这不是 AI "犯错了"。这是 AI 在做它最擅长的事——找一条能到终点的路。它只是不负责告诉你,这条路是不是最干净的,路边会不会踩到钉子,你明天要不要加班。
那怎么办?
搞清楚原理以后,解决思路反而清楚了。三个建议,都很务实:
第一,关键操作指定专用工具。
写 Prompt 或设计 AI 工作流时,如果涉及创建文件,显式告诉它用 Write,不要让它自己选。 把"创建文件"和"执行命令"这两件事在语义上切开——前者叫 write,后者叫 run。别含糊。
第二,关键输出做后置校验。
在流程的尾巴上加一道检查。JSON 文件就跑一次 python -m json.tool,或者让 AI 用十六进制看一眼文件头。这不是过度防御——在"AI 输出不可靠"这个前提下,这就是合理的工程实践。
那三个字节的 BOM,查一下就现行。但如果没人看、没人查,它能一直潜伏到生产环境,给你炸一个大的。
第三,接受 AI 的"野路子倾向",把它当特性,别当 Bug。
AI 会用 Shell 绕路,这不是偶发事故,是概率模型的底层属性。
与其指望它永远选对工具、永远走正道,不如在设计流程时,默认假设"它随时可能用命令行写文件",然后在接收端做好防御。
就像你写后端 API,从来不会假设前端一定传对了字段。把 AI 的输出,也当成不可信的外部输入来处理。
写在最后
一个 1990 年代定义的三个字节,在 2026 年的 AI 生成代码里出现了。
这不是编码史的老黄历没翻过去。这是 AI 工具链的一幅真实切面:
一套精心设计的原语工具,被扔进了一个概率驱动的决策黑盒。AI 有时会精准地用 Write 写出干净的 UTF-8,有时会随手敲一行 PowerShell,然后给你留下一个 EF BB BF。
它在工具选择上没有对错观,只有概率。
而你的工程质量,不能押在概率上。
扣子智能体教程
https://space.bilibili.com/26079586
夜雨聆风