AI编程工具为何把CRLF文件改成全文件Diff?Codex修复解析

AI编码代理的编辑工具将CRLF文件以LF写回,导致git diff显示全文件变更;Codex已修复,根治方案是.gitattributes加CI检查。
文章从AI编码代理修改Windows换行文件却产生全文件diff的现象切入,指出根因是代理编辑工具将CRLF行结尾静默转换为LF。Git在索引与工作区边界按gitattributes和core.autocrlf规则处理换行,但历史CRLF文件常因缺少显式text标记而被放过,成为代理破坏的重灾区。作者给出五条命令复现问题,并介绍git ls-files --eol与git diff --ignore-cr-at-eol两个诊断命令。Codex 0.162.0的apply_patch已默认保留现有CRLF,Claude Code虽多次修复但后续issue归咎不一。长期方案是配置.gitattributes、用git add --renormalize一次性规范化,并在CI中用git ls-files --eol拦截CRLF入库;同时建议按轮次审查代理diff,以便在问题发生的那一轮就抓住它。
AI编程工具为何把CRLF文件改成全文件Diff?Codex修复解析
你让编码代理修改 deploy.bat 里的一个路径,它照做了。但拉取请求显示文件每一行都被删除又添加,评审同事问你到底重写了什么。你什么都没重写——代理的编辑工具读入一个使用 CRLF(\r\n,Windows 换行约定)的行结尾文件,写回时却用了 LF(\n)。在 git 眼里,每一行都因为一个不可见的字节而不同。

这不是孤例。据 Claude Code 用户提交的 issue #62713,一次 Edit 调用后,“插入块之外的几十行显示为已更改——尽管它们的文本内容完全相同”。另一份报告 issue #64599 指出,Write 工具剥离 CRLF 后,“下一次提交时 git diff 报告每一行都变了”。而 Codex CLI 在 10 月 8 日修改了自己的补丁工具:release 0.162.0 的更新日志写明“在 apply_patch 更新中保留现有 CRLF 行结尾,无需额外开启选项”。
五条命令复现全文件Diff
在 macOS、Linux 或 Git Bash 上运行以下脚本。第二行把 core.autocrlf 固定为关闭,避免全局配置干扰结果:
git init -q crlf-demo && cd crlf-demo
git config core.autocrlf false
printf 'one\r\ntwo\r\nthree\r\n' > run.bat
git add run.bat && git commit -qm "CRLF file"
printf 'one\ntwo\nTHREE\n' > run.bat
第三行写入一个 Windows 风格文件;第四行提交时保持 CRLF 字节原样,因为没有规则要求转换;第五行模拟代理行为:同一个文件只改一个词,但所有行结尾都写成 LF。此时运行 git diff --stat,你会看到三处删除和三处插入——在一个只改了一个词的文件上。真实文件中,这就是每一行。
问题的根源在于多个工具对换行格式没有共识,而 git 无法告诉你谁是对的。
Git 到底在比较什么
Git 在这里维护三份文件副本:已提交的 blob、索引(git add 暂存的内容)和工作区文件。git diff 比较的是工作区与索引。行结尾转换发生在两者边界上,且仅当 git 将该文件视为文本时进行。
据 gitattributes 文档,当 text 属性被设置时,“文件的行结尾在索引中规范化为 LF”,检出时“可能根据 eol 属性、Git 配置和平台从 LF 转换为 CRLF”。eol=crlf 强制该路径在工作区使用 CRLF;-text 关闭转换。
没有属性生效时,core.autocrlf 决定行为。git-config 文档说明,设为 true “等同于对所有文件设置 text 属性为 'auto',并将 core.eol 设为 'crlf'”;input 在提交时转换但检出时不转换。GitHub 官方指南建议 Windows 用 true,macOS 和 Linux 用 input。
陷阱在于 text=auto 的工作方式(autocrlf=true 也使用它)。gitattributes 文档指出:“如果它是文本且文件尚未以 CRLF 结尾存入 Git,则转换行结尾。”一个在任何人设置规范化之前就以 CRLF 提交的文件,无论 core.autocrlf 怎么设置,在所有机器上都会被放过,直到有 .gitattributes 规则将其标记为 text。这正是代理会破坏的那类文件。
| 文件存储方式 | LF 重写后 git diff 的表现 |
|---|---|
索引中为 CRLF(原始提交或在 -text 下) | 每一行都显示更改 |
| 索引中为 LF,按文本处理 | 只显示真正更改的行,但磁盘文件已变成 LF |
第二行看起来无害,但并不总是如此。diff 干净了,工作区文件却在下次检出重写前丢失了 CRLF。期间需要 CRLF 的工具会出错。有用户报告因 .NET XML 解析器“因行结尾为 LF 而非 CRLF 而静默失败”损失了约四小时。
text=auto 是 gitattributes 中的一种自动检测模式,它让 Git 在提交时判断文件是否为文本:若是,则把行结尾规范化为 LF 存入索引;若含 NUL 字节等二进制特征,则不做转换。关键在于“规范化仅在首次入库时发生”——如果文件以 CRLF 直接进入索引且从未被标记为文本,text=auto 不会追溯性地改写它。这正是历史遗留的 Windows 脚本文件成为代理破坏重灾区的原因:它们在规范化规则建立之前就已提交,Git 视其为“已经符合要求”,直到有显式 .gitattributes 规则将其标记为 text 才触发转换。理解这一点,才能解释为何 core.autocrlf=true 对某些 CRLF 文件毫无作用。
两个命令看清真相
回到演示仓库,运行:
git ls-files --eol run.bat
git diff --ignore-cr-at-eol run.bat
git ls-files --eol 为每个文件打印三个字段:i/ 表示索引中的行结尾,w/ 表示工作区,attr/ 表示 git 应用的属性。前两个字段的值可能是 lf、crlf、mixed、none 或 -text。对于 run.bat,你应该看到 i/crlf、w/lf 和空的 attr/。这一行就是完整诊断:以 CRLF 提交,现在磁盘上是 LF,没有规则告诉 git 该怎么办。
--ignore-cr-at-eol 让 git diff “在比较时忽略行尾的回车符”。加上它,diff 会缩小到唯一真实变化:three 变成 THREE。如果一个巨大的 diff 在该标志下变小,其余部分就是行结尾造成的。
不带路径运行 git ls-files --eol 可以查看整个仓库。w/ 列中任何 mixed 都表示某个文件被部分改成了 LF、部分保留 CRLF——这通常是行结尾不敏感的工具做部分编辑后留下的痕迹。
git ls-files --eol 的 attr/ 字段为空与显示属性的区别至关重要。空值意味着没有任何 gitattributes 规则或 core.autocrlf 配置对该路径生效,Git 完全按字节原样处理——文件保持什么行结尾就存什么,不会告警。若该字段显示 text 或 text eol=crlf,则说明转换规则已激活,此时 w/lf 会明确提示磁盘副本偏离了预期。而 i/crlf 与 w/lf 的组合则揭示了一个常见困境:索引里存的是 CRLF 字节,工作区却被写成了 LF;由于没有转换规则,Git 无法自动修复,只能由人工介入。把 attr/ 列作为诊断的第一站,可以快速区分“规则缺失”和“规则被违反”两种截然不同的故障。
Codex 0.162 改了什么,Claude Code 更新日志怎么说
Codex。 0.162.0 更新背后的拉取请求 openai/codex#51203 指出,用 apply_patch 更新 CRLF 文件“此前会将行结尾规范化为 LF,除非显式启用保留”。现在该工具默认保留文件现有行结尾,模型工具调用和独立 apply_patch 运行都一样。该 PR 针对的是现有文件的更新,并未说明全新文件使用什么行结尾,所以 .gitattributes 仍然决定这一点。
Claude Code。 更新日志记录了对这些工具的更早修复。版本 2.1.77“修复了 Write 工具在覆盖 CRLF 文件或在 CRLF 目录中创建文件时静默转换行结尾的问题”。版本 2.1.89“修复了 Edit/Write 工具在 Windows 上重复 CRLF 以及剥离 Markdown 硬换行(两个尾随空格)的问题”。最新两个版本也各含一个 CRLF 修复,但都不是关于 Edit 或 Write:2.1.295 修复显示问题,2.1.296 修复 Workflow 工具拒绝 CRLF 行结尾脚本文件的问题。
在这些 Edit 和 Write 修复之后提交的用户报告仍然互相矛盾。#62713 归咎于 Edit;#63715 则说“Edit 工具正确保留了行结尾——通过 Bash 调用的 sed 没有”,指向 Git Bash 下的 sed -i;#64599 归咎于 Write。三个 issue 都被机器人以不活跃为由关闭,没有维护者回复。无论实际执行写入的是哪个工具(Edit、Write、apply_patch 还是模型选择的 shell 单行命令),都要检查字节。
一次性修复:.gitattributes
要就地修复演示文件,用 Perl 单行命令将所有行结尾转回 CRLF。它对混合文件也有效:
perl -pi -e 's/\r?\n/\r\n/' run.bat
git diff run.bat
现在 diff 只剩那一个词。但这只修复了一个文件一次。长期方案是在仓库中定策略,让每个代理和每个队友都继承它。参照 GitHub 示例文件的结构:
* text=auto
*.bat text eol=crlf
*.cmd text eol=crlf
*.sln text eol=crlf
*.png binary
添加该文件不会转换已提交的内容。gitattributes 文档给出了一次性步骤,需在干净工作区运行:
git add --renormalize .
git status
git commit -m "Normalize line endings"
该提交之后,索引存储 LF,Windows 专用文件检出时为 CRLF。如果代理之后向 run.bat 写入 LF,git diff 只显示真实变化,而 git ls-files --eol 会显示 w/lf 旁边是 attr/text eol=crlf,告诉你磁盘上的副本需要在 Windows 工具读取前修复。要阻止 CRLF 重新混入索引,这个 CI 步骤可在任何文件以 CRLF 存储时失败:
! git ls-files --eol | grep 'i/crlf'
git add --renormalize . 的作用是让 Git 重新应用当前生效的 gitattributes 规则,把工作区文件按新规则重新暂存。它不会改变文件在磁盘上的字节,只会更新索引中的存储形式:对于标记为 text 的文件,索引将统一存为 LF;检出时再依据 eol 属性还原为 CRLF 或保持 LF。这一步必须在干净工作区运行,因为 renormalize 会暂存所有受规则影响的文件,若混有未提交的修改,容易把无关变更带入同一个提交。执行后先用 git status 确认变更范围,再单独提交,可以避免行结尾规范化与业务改动纠缠在一起,让历史记录保持清晰可追溯。
在问题发生的那一轮抓住它
当全文件 diff 单独出现时很容易发现。但当代理工作了很多轮,坏的重写混杂在最终 PR 的几十个文件里时,它就会隐藏起来。
笔者构建了 Hermes IDE,请带着这一点权衡以下内容。Hermes 保存每一轮代理操作,其 Review Desk(⌘G,Windows/Linux 为 Ctrl+Shift+G)按轮次列出更改,每轮显示文件数和增删行数。一个本应只改 run.bat 一行的轮次却显示整个文件被删除又添加,在该轮就会显眼;你可以对该轮做 diff 或恢复,支持撤销。Hermes 不替你检查行结尾——git ls-files --eol 仍然是检查手段,在数字看起来不对时运行它。

Hermes 免费下载,以 BSL 1.1 源码可用,支持 macOS、Windows 和 Linux。它与 Nous Research、Anthropic、OpenAI 或任何代理厂商无关。
换个角度看这个问题
这个 bug 本质上是两个写入者对一种没人写下来的格式产生分歧,而 git 无法告诉你哪个是对的。事件驱动系统在更大规模上以同样方式失败:一个服务改变了它发出的内容,另一个服务却还在读旧结构。关键教训是——在 AI 编程工具链中,把行结尾策略写进 .gitattributes,在 CI 中用 git ls-files --eol 把关,在代理工作流中按轮次审查 diff,比事后追查每个工具的行为更可靠。
相关推荐

OpenAI新模型Bel曝光:10万亿参数,或成AGI基座
Reddit爆料称OpenAI新模型Bel即将问世,总参数超10万亿,或作为GPT-6之后的基础模型甚至AGI门槛基座。本文拆解爆料关键信息并评估可信度。

Claude Fable 5/5.1报错事件:30分钟故障复盘
Claude Fable 5与Fable 5.1出现错误率升高的服务故障,官方状态页显示影响约35分钟,从Investigating到Resolved仅用37分钟。本文还原故障时间线,分析对其他Claude模型的波及范围,并给出开发者重试、多模型路由与监控告警的应对建议。

Kimi K3.1开源模型实测:3万亿参数挑战闭源巨头
Kimi K3.1 开源权重模型深度实测:3万亿参数MoE架构、100万token上下文,3D渲染惊艳但空间推理与事实准确性存短板。基准逼近Opus 5.5,每任务成本仅2.11美元,开源前沿的真实水位全解析。