- Book: Event-Driven Architecture Pocket Guide
- The series: Pocket Guides for Developers — 8 short books on agents, RAG, prompts, observability, events, databases and system design — the whole series
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
You asked the agent to change one path in deploy.bat. It did. Then the pull request showed every line of the file deleted and added back, and your reviewer asked what you rewrote.
You rewrote nothing you can see. The agent's edit tool read a file whose lines end in CRLF (\r\n, the Windows convention) and wrote it back with LF (\n). To git, every line now differs by one invisible byte.
Developers have filed this. A Claude Code user reported the same thing in issue #62713: after an Edit call, "dozens of lines outside the inserted block showed as changed — even though their text content was identical". Another report, #64599, says that after the Write tool stripped CRLF, "Git diff reports every line changed on the next commit." And Codex CLI changed its own patch tool on October 8: release 0.162.0 lists "Preserve existing CRLF line endings in apply_patch updates without requiring an opt-in."
Break a repo in five lines
Run this on macOS, Linux or Git Bash. The second line pins core.autocrlf off so the result doesn't depend on your global config:
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
Line three writes a Windows-style file. Line four commits it with its CRLF bytes intact, because nothing told git to convert them. Line five plays the agent: the same file, one word changed, every ending written as LF.
Now run git diff --stat. It should count three deletions and three insertions for a file where one word changed. In a real file, that is every line.
What git is actually comparing
Git keeps three copies of a file that matter here: the committed blob, the index (what git add staged), and the file in your working tree. git diff compares the working tree to the index. Line-ending conversion happens at the border between those two, and only when git treats the file as text.
The gitattributes docs spell out the rules. When the text attribute is set, "the file's line endings are normalized to LF in the index", and on checkout they "may be converted from LF to CRLF depending on the eol attribute, the Git config, and the platform." eol=crlf forces CRLF in the working tree for that path. -text turns conversion off.
When no attribute applies, core.autocrlf decides. The git-config docs say setting it to true "is the same as setting the text attribute to 'auto' on all files and core.eol to 'crlf'", and input converts on commit but not on checkout. GitHub's own guide recommends true on Windows and input on macOS and Linux.
The catch is in how text=auto works, which autocrlf=true also uses. Per the gitattributes docs: "If it is text and the file was not already in Git with CRLF endings, line endings are converted." A file that was committed with CRLF before anyone set up normalization is left alone on every machine, whatever core.autocrlf says, until a .gitattributes rule marks it as text. That is the file your agent breaks.
| How the file is stored | What an LF rewrite shows in git diff
|
|---|---|
CRLF in the index (committed raw, or under -text) |
Every line changed |
| LF in the index, treated as text | Only the line that changed, but the file on disk is now LF |
The second row looks harmless and isn't always. The diff is clean, yet the working-tree file lost its CRLF until the next checkout rewrites it. Tools that need CRLF break in between. One user reported losing about four hours to a .NET XML parser that "failed silently because the line endings were LF instead of CRLF."
Read the evidence: two commands
Back in the demo repo:
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 attribute git applies. Each of the first two reads lf, crlf, mixed, none or -text. For run.bat you should see i/crlf, w/lf and an empty attr/. That one line is the whole diagnosis: committed as CRLF, now LF on disk, no rule telling git what to do about it.
--ignore-cr-at-eol tells git diff to "ignore carriage-return at the end of line when doing a comparison" (git-diff docs). With it, the diff shrinks to the one real change, three becoming THREE. If a huge diff turns small under that flag, the rest of it was line endings.
Run git ls-files --eol with no path and you get the whole repo. Any mixed in the w/ column is a file where something wrote LF on some lines and kept CRLF on others, which is often what a partial edit by a line-ending-blind tool leaves behind.
What Codex 0.162 changed, and what Claude Code's changelog says
Codex. The pull request behind the 0.162.0 note, openai/codex#51203, says that updating a CRLF file with apply_patch "previously normalized its line endings to LF unless preservation was explicitly enabled." Now the tool keeps the file's existing endings by default, for model tool calls and for standalone apply_patch runs alike. The PR is about updates to existing files. It doesn't say what line endings a brand-new file gets, so your .gitattributes still decides that one.
Claude Code. The changelog records earlier fixes to these exact tools. Version 2.1.77 "Fixed Write tool silently converting line endings when overwriting CRLF files or creating files in CRLF directories." Version 2.1.89 "Fixed Edit/Write tools doubling CRLF on Windows and stripping Markdown hard line breaks (two trailing spaces)." The two newest releases each carry a CRLF fix as well, and neither is about Edit or Write. Version 2.1.295 "Fixed short command output with Windows line endings (CRLF) being drawn on a single line", which is a display fix. Version 2.1.296 "Fixed the Workflow tool refusing script files with CRLF line endings, such as files checked out on Windows."
User reports filed after those Edit and Write fixes still disagree. #62713 blames Edit. #63715 says "The Edit tool preserves line endings correctly -- sed via Bash does not," pointing at sed -i under Git Bash instead. #64599 blames Write. A bot closed all three for inactivity with no maintainer reply. Whichever tool did the write (Edit, Write, apply_patch or a shell one-liner the model picked), check the bytes.
Fix it once, in .gitattributes
To repair the demo file in place, turn every line ending back into CRLF with a Perl one-liner. It works on mixed files too:
perl -pi -e 's/\r?\n/\r\n/' run.bat
git diff run.bat
The diff is now the one word. That fixes one file, once. The lasting fix is to decide the policy in the repo, where every agent and every teammate inherits it. Following the shape of GitHub's example file:
* text=auto
*.bat text eol=crlf
*.cmd text eol=crlf
*.sln text eol=crlf
*.png binary
Adding the file doesn't convert anything already committed. The gitattributes docs give the one-time step, run from a clean working tree:
git add --renormalize .
git status
git commit -m "Normalize line endings"
After that commit the index stores LF, and Windows-only files come out of checkout as CRLF. If an agent later writes LF into run.bat, git diff shows only the real change, and git ls-files --eol shows w/lf next to attr/text eol=crlf, which tells you the copy on disk needs fixing before a Windows tool reads it. To keep CRLF from creeping back into the index, this CI step fails if any file is stored with CRLF:
! git ls-files --eol | grep 'i/crlf'
Catch it on the turn that caused it
A whole-file diff is easy to spot when it's alone. It hides when the agent worked through many turns and the bad rewrite is one file among dozens in the final pull request.
I build Hermes IDE, so weigh this with that in mind. Hermes saves every agent turn, and its Review Desk (⌘G, Ctrl+Shift+G on Windows and Linux) lists the changes turn by turn, each with its file count and lines added and removed. A turn that was supposed to touch one line of run.bat and shows the whole file removed and re-added stands out on that turn, and you can diff or restore that single turn, with undo. Hermes doesn't check line endings for you. git ls-files --eol is still the check, run when the numbers look wrong.

Hermes Review Desk (⌘G), By turn view: each turn with its file count and lines added and removed, turn 1's diff, and a commit message drafted from the turns.
Hermes is free to download, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It isn't affiliated with Nous Research, Anthropic, OpenAI or any agent vendor. Download it here, and the code is on GitHub.
If this was useful
This bug is two writers disagreeing about a format nobody wrote down, and git can't tell you which one was right. Event-driven systems fail the same way at a larger scale, when one service changes what it emits and another keeps reading the old shape. The Event-Driven Architecture Pocket Guide is about those traps and the patterns that keep producers and consumers honest. It is one of 8 short books in the Pocket Guides for Developers series, on agents, RAG, prompts, observability, events, databases and system design: the whole series is here.

Top comments (0)