Why AI Coding Tools Turn CRLF Files into Whole-File Diffs? Codex Fix Explained

AI coding agents' edit tools write CRLF files back as LF, causing git diff to show whole-file changes; Codex has fixed it, and the root solution is .gitattributes plus CI checks.
The article starts from the phenomenon of AI coding agents producing whole-file diffs when editing Windows line-ending files, pointing to the root cause: agent edit tools silently convert CRLF to LF. Git handles line endings at the index/working-tree boundary according to gitattributes and core.autocrlf rules, but legacy CRLF files often slip through due to missing explicit text markers, making them prime targets for agent breakage. The author provides five commands to reproduce the issue and introduces two diagnostic commands: git ls-files --eol and git diff --ignore-cr-at-eol. Codex 0.162.0's apply_patch now preserves existing CRLF by default; Claude Code has fixed it multiple times but later issues blame different tools. The long-term solution is configuring .gitattributes, normalizing once with git add --renormalize, and blocking CRLF from entering the index in CI via git ls-files --eol; reviewing agent diffs round by round is also recommended to catch the problem in the round where it happens.
Why AI Coding Tools Turn CRLF Files into Whole-File Diffs? Codex Fix Explained
You ask a coding agent to change a single path in deploy.bat, and it does. But the pull request shows every line of the file deleted and re-added, and a reviewing colleague asks what exactly you rewrote. You rewrote nothing — the agent's edit tool read in a file using CRLF (\r\n, the Windows line-ending convention) and wrote it back with LF (\n). To git, every line differs by one invisible byte.

This is not an isolated case. According to issue #62713 filed by a Claude Code user, after a single Edit call, "dozens of lines outside the inserted block show as changed — even though their text content is identical." Another report, issue #64599, notes that after the Write tool stripped CRLF, "the next commit's git diff reports every line changed." And Codex CLI changed its own patch tool on October 8: the release notes for 0.162.0 state that it "preserves existing CRLF line endings in apply_patch updates, with no extra option needed."
Reproducing the Whole-File Diff in Five Commands
Run the following script on macOS, Linux, or Git Bash. The second line pins core.autocrlf to off so global configuration doesn't skew the result:
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
The third line writes a Windows-style file; the fourth line commits it keeping the CRLF bytes as-is, because no rule requires conversion; the fifth line simulates the agent's behavior: the same file with one word changed, but all line endings written as LF. Now run git diff --stat and you'll see three deletions and three insertions — on a file where only one word changed. On a real file, that's every line.
The root of the problem is that multiple tools have no consensus on newline format, and git can't tell you who's right.
What Git Is Actually Comparing
Git maintains three copies of the file here: the committed blob, the index (what git add stages), and the working-tree file. git diff compares the working tree against the index. Line-ending conversion happens at the boundary between the two, and only when git treats the file as text.
According to the gitattributes documentation, when the text attribute is set, "a file's line endings are normalized to LF in the index," and on checkout "may be converted from LF to CRLF depending on the eol attribute, Git configuration, and platform." eol=crlf forces CRLF in the working tree for that path; -text disables conversion.
When no attribute applies, core.autocrlf decides the behavior. The git-config documentation states that setting it to true "is equivalent to setting the text attribute to 'auto' on all files and setting core.eol to 'crlf'"; input converts on commit but not on checkout. GitHub's official guidance recommends true on Windows and input on macOS and Linux.
The trap is how text=auto works (which autocrlf=true also uses). The gitattributes documentation states: "If it is text and the file is not already stored in Git with CRLF endings, line endings are converted." A file committed with CRLF before anyone set up normalization is let through on every machine regardless of core.autocrlf, until a .gitattributes rule marks it as text. That's exactly the kind of file agents break.
| How the file is stored | How git diff behaves after an LF rewrite |
|---|---|
CRLF in the index (original commit or under -text) | Every line shows as changed |
| LF in the index, treated as text | Only genuinely changed lines show, but the on-disk file is now LF |
The second row looks harmless, but isn't always. The diff is clean, yet the working-tree file loses CRLF until the next checkout rewrites it. Tools that need CRLF in the meantime break. One user reported losing about four hours because a .NET XML parser "silently failed because line endings were LF instead of CRLF."
text=auto is an auto-detection mode in gitattributes that lets Git decide at commit time whether a file is text: if so, it normalizes line endings to LF in the index; if it contains binary traits like NUL bytes, it doesn't convert. The key point is that "normalization happens only on first entry" — if a file goes into the index with CRLF and was never marked as text, text=auto won't retroactively rewrite it. This is exactly why legacy Windows script files are the worst hit by agent breakage: they were committed before normalization rules existed, and Git considers them "already compliant" until an explicit .gitattributes rule marks them as text. Understanding this explains why core.autocrlf=true does nothing for certain CRLF files.
Two Commands to See the Truth
Back in the demo repo, run:
git ls-files --eol run.bat
git diff --ignore-cr-at-eol run.bat
git ls-files --eol prints three fields per file: i/ for the line endings in the index, w/ for the working tree, and attr/ for the attributes git applies. The first two fields can be lf, crlf, mixed, none, or -text. For run.bat, you should see i/crlf, w/lf, and an empty attr/. That line is the full diagnosis: committed as CRLF, now LF on disk, and no rule telling git what to do.
--ignore-cr-at-eol makes git diff "ignore carriage returns at the end of lines when comparing." Add it and the diff shrinks to the only real change: three becomes THREE. If a huge diff shrinks under that flag, the rest is line-ending noise.
Run git ls-files --eol without a path to see the whole repository. Any mixed in the w/ column means a file was partially changed to LF and partially left as CRLF — usually the fingerprint left by a partial edit from a line-ending-insensitive tool.
The distinction between an empty and a populated attr/ field in git ls-files --eol is crucial. An empty value means no gitattributes rule or core.autocrlf setting applies to that path, and Git handles it purely byte-for-byte — whatever line endings the file has are stored as-is, with no warning. If the field shows text or text eol=crlf, a conversion rule is active, and a w/lf then clearly signals that the on-disk copy deviates from expectations. The combination of i/crlf and w/lf reveals a common predicament: the index stores CRLF bytes, but the working tree was written as LF; with no conversion rule, Git can't fix it automatically and human intervention is required. Treating the attr/ column as your first diagnostic stop quickly distinguishes two very different failures: "rule missing" and "rule violated."
What Codex 0.162 Changed, and What the Claude Code Changelog Says
Codex. The pull request behind the 0.162.0 update, openai/codex#51203, notes that updating CRLF files with apply_patch "previously normalized line endings to LF unless preservation was explicitly enabled." Now the tool preserves a file's existing line endings by default, both for model tool calls and standalone apply_patch runs. The PR targets updates to existing files and doesn't state what line endings brand-new files use, so .gitattributes still decides that.
Claude Code. The changelog records earlier fixes to these tools. Version 2.1.77 "fixed a bug where the Write tool silently converted line endings when overwriting CRLF files or creating files in CRLF directories." Version 2.1.89 "fixed the Edit/Write tools duplicating CRLF on Windows and stripping Markdown hard line breaks (two trailing spaces)." The two most recent versions each contain a CRLF fix too, but neither is about Edit or Write: 2.1.295 fixes a display issue, and 2.1.296 fixes the Workflow tool rejecting CRLF line-ending script files.
User reports filed after these Edit and Write fixes still contradict each other. #62713 blames Edit; #63715 says "the Edit tool correctly preserved line endings — the sed invoked through Bash did not," pointing to sed -i under Git Bash; #64599 blames Write. All three issues were closed by a bot for inactivity, with no maintainer reply. Whichever tool actually performed the write (Edit, Write, apply_patch, or a shell one-liner chosen by the model), check the bytes.
The One-Time Fix: .gitattributes
To fix the demo file in place, use a Perl one-liner to convert all line endings back to CRLF. It works on mixed files too:
perl -pi -e 's/\r?\n/\r\n/' run.bat
git diff run.bat
Now the diff shows just that one word. But this fixes one file once. The long-term solution is to set policy in the repository so every agent and every teammate inherits it. Following the structure of GitHub's example file:
* text=auto
*.bat text eol=crlf
*.cmd text eol=crlf
*.sln text eol=crlf
*.png binary
Adding this file won't convert already-committed content. The gitattributes documentation gives the one-time steps, to be run in a clean working tree:
git add --renormalize .
git status
git commit -m "Normalize line endings"
After that commit, the index stores LF, and Windows-specific files check out as CRLF. If an agent later writes LF to run.bat, git diff shows only the real change, and git ls-files --eol shows w/lf next to attr/text eol=crlf, telling you the on-disk copy needs fixing before a Windows tool reads it. To stop CRLF from slipping back into the index, this CI step can fail whenever any file is stored as CRLF:
! git ls-files --eol | grep 'i/crlf'
git add --renormalize . works by having Git re-apply the currently effective gitattributes rules and re-stage working-tree files according to the new rules. It doesn't change the bytes on disk, only the stored form in the index: for files marked text, the index stores LF uniformly; on checkout they're restored to CRLF or kept as LF based on the eol attribute. This step must be run in a clean working tree, because renormalize stages every file affected by the rules, and any uncommitted changes mixed in can drag unrelated modifications into the same commit. After running it, confirm the scope of changes with git status, then commit separately — this keeps line-ending normalization from getting tangled with business changes and keeps history clean and traceable.
Catch It in the Round Where It Happens
A whole-file diff is easy to spot when it appears on its own. But when an agent has worked for many rounds and the bad rewrite is buried among dozens of files in the final PR, it hides.
The author built Hermes IDE; weigh the following with that in mind. Hermes saves every round of agent operations, and its Review Desk (⌘G, or Ctrl+Shift+G on Windows/Linux) lists changes by round, showing file counts and added/deleted line counts per round. A round that should have changed one line of run.bat but shows the whole file deleted and re-added stands out right there; you can diff or revert that round, with undo support. Hermes doesn't check line endings for you — git ls-files --eol remains the check, to run when the numbers look wrong.

Hermes is free to download, source-available under BSL 1.1, and supports macOS, Windows, and Linux. It is not affiliated with Nous Research, Anthropic, OpenAI, or any agent vendor.
Another Angle on the Problem
This bug is essentially two writers disagreeing about a format nobody wrote down, and git can't tell you which is right. Event-driven systems fail the same way at larger scale: one service changes what it emits while another is still reading the old structure. The key lesson is this — in an AI coding toolchain, writing line-ending policy into .gitattributes, gating it in CI with git ls-files --eol, and reviewing agent diffs round by round is more reliable than chasing down each tool's behavior after the fact.
Related articles

AgentCore in Practice: Connecting Custom Tools and MCP Servers to Agents
DebuggerCafe tutorial shows how to connect custom tools and MCP servers to Amazon Bedrock AgentCore agents, covering tool contract design, MCP reuse, and production concerns.

816B Parameters Scores Only 39? The Real Cost-Effectiveness of the Open-Source LLM Parameter Race
Meta's 816B Muse Spark Open scores just 39 on Artificial Analysis, while 180B Qwen3.8-Flash-Next scores 40. This article analyzes 11 open-weight models' parameters vs. scores, exploring the real impact of activated parameters, quantized size, and deployment cost.

Aerial Manipulators: The Next Step for Drones from 'Seeing' to 'Changing'
Starting from a Reddit discussion, this article analyzes the technical challenges, potential applications, and deployment bottlenecks of aerial manipulators, exploring the key challenges for drones moving from environmental perception to physical manipulation.