Git LF CRLF Warning — Fix Line Endings
Set core.autocrlf per OS to silence the LF warning, then lock .gitattributes rules and renormalize so endings stay mixed-free..
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
- ✓Basic git add, commit, and status workflow
- ✓A repo cloned on your usual OS
- ✓A text editor with line-ending settings
- The LF/CRLF warning means Git converted line endings on checkout or commit; your file is safe but your settings are inconsistent.
- Set core.autocrlf true on Windows, input on macOS and Linux, or false only when .gitattributes controls everything.
- Lock endings with .gitattributes: text=auto plus explicit eol rules for scripts (.sh text eol=lf) and Windows files.
- Renormalize once with git add --renormalize, then enforce EditorConfig and pre-commit checks so mixed endings stop returning.
Think of line endings as line breaks in notebooks. Windows ends each line with two marks, Mac and Linux use one. When teammates share one notebook through Git, someone's editor converts the marks on the way in and warns you about it. The warning is Git saying I translated the breaks so everyone can read them. The fix is agreeing once on which marks the shared notebook uses, then letting each person's desk copy translate automatically.
You add a file, and Git prints: warning: LF will be replaced by CRLF the next time Git touches it. Or the reverse on a Mac: CRLF will be replaced by LF. The commit succeeds. The file looks fine. Yet the warning returns on every other file, diffs show whole files changed when you touched one line, and a shell script fails with $'\r': command not found.
The reflex is to ignore it because it's only a warning. That works until mixed endings poison a build: a .sh checked out with CRLF won't run on Linux, a .bat with LF confuses old Windows tools, and phantom diffs bury real code review changes under thousands of line-ending-only lines.
The cause is three layers disagreeing: your core.autocrlf setting, the repo's .gitattributes rules, and your editor's save behavior. Windows, macOS, and Linux each want a different checkout style, and without committed rules every clone negotiates endings on its own.
This guide shows what the warning actually did, which autocrlf value fits your OS, how .gitattributes locks the contract, how to renormalize once, and how mixed endings break diffs and builds. You'll silence the warning permanently with settings that travel with the repo.
Reading the Warning: What Git Actually Did
Git's line-ending warnings describe a conversion, not corruption. LF will be replaced by CRLF means the file is stored with LF in the repo but will check out with CRLF on your Windows disk. CRLF will be replaced by LF means the reverse: your working file has CRLF but Git will store LF. In both cases your data is intact — Git translated the invisible characters at line ends so the file matches your platform's convention.
The warning fires at predictable moments: on git add when the working file's endings differ from what the index will store, and on git checkout when the stored endings differ from what your disk will get. A warning that appears once per file is Git announcing its translation policy. The same warning on every commit is Git telling you the policy was never locked down, so it renegotiates per file.
Diagnose with two commands that show both sides. git ls-files --eol lists each file's index ending versus working-tree ending, so you can see at a glance which files are mixed. git diff piped through cat -A reveals ^M carriage-return markers on changed lines, proving CRLF is present where you expected LF.
Don't suppress the messenger. The warning is the cheapest signal you'll get that three layers — your config, the repo attributes, and your editor — disagree. Read one warning fully, fix the policy behind it, and the rest of the warnings disappear with it.
core.autocrlf True, Input, False: Pick Per OS
core.autocrlf controls the default translation when no .gitattributes rule covers a file. true checks out CRLF and commits LF: the Windows default, where Notepad-era tools expect carriage returns on disk but the repo stays LF-clean. input converts CRLF to LF on commit but checks out verbatim: the macOS and Linux default, where disks already want LF and only inbound Windows endings need cleaning. false disables all translation: what you save is what you commit, byte for byte.
Pick by OS unless the repo already pins everything. Windows developers should use true so local tools see CRLF while the shared history stays LF. macOS and Linux developers should use input so stray CRLF from cross-platform edits gets normalized on commit. false fits only repos where .gitattributes covers every path and you want zero magic — bare servers, containers, and Linux-only teams with strict attributes.
Set it at the right scope and verify. git config --global core.autocrlf true sets your user default on Windows; --global core.autocrlf input does the macOS and Linux side. Repo-local config overrides global, so check git config --list --show-origin | grep autocrlf when behavior surprises you — a cloned repo template may have set its own value.
Remember this setting is personal, not shared. It never travels with git push, which is why .gitattributes must carry the team contract. Autocrlf handles your disk; attributes handle the repo.
.gitattributes Text and EOL: Rules That Travel
Unlike autocrlf, .gitattributes commits with the repo and applies identically on every clone. The text attribute enables normalization for a path; eol pins the checkout style. * text=auto tells Git to normalize line endings on commit for files it detects as text while leaving detected binaries alone. That's the safe baseline every repo should start from, because it makes the shared history LF without touching images or archives.
Pin the exceptions explicitly. .sh text eol=lf forces shell scripts to LF on every OS, which is non-negotiable for Linux execution. .bat and .ps1 often want eol=crlf for legacy Windows tooling. .png, .jpg, and .zip want -text or binary so Git never translates a byte. Language-agnostic wildcards beat enumerating every extension: pin by execution need, not by editor preference.
Order and specificity matter. Later lines override earlier ones, so put the * text=auto baseline first and specific pins after. Use git check-attr -a <path> to see the effective attributes for any file — it resolves the pattern stack and shows exactly which line won. When a file misbehaves, check-attr names the rule to fix instead of guessing.
Commit the file early and renormalize once. A three-line .gitattributes merged this week prevents three years of warning-by-warning negotiation across every contributor's machine.
Renormalize the Repo: Fix History Without Chaos
Adding .gitattributes doesn't rewrite files already in the index — it only governs future adds and checkouts. Renormalization applies the new rules to everything in one controlled pass. git add --renormalize . re-scans every tracked file, converts index endings per the fresh attributes, and stages exactly the files whose stored bytes must change. You review that set, commit once, and every future clone inherits clean endings.
Run it in sequence, not as a guess. Commit or stash all work first so the renormalize diff contains only ending changes. Commit the .gitattributes file itself. Then run git add --renormalize ., inspect with git status --short and git diff --stat, and commit with a message like Normalize line endings per .gitattributes. Push and have each teammate pull and re-checkout affected paths so stale working-tree endings don't get recommitted.
Expect a noisy-but-safe diff. Files that were stored CRLF flip to LF in the index; working-tree files may show as modified until re-checked out. That's the one-time cost. Announce the commit to the team, merge it during a quiet window, and ask open PRs to rebase after it lands so they don't resurrect old endings.
Never rewrite history for this. A single normalization commit at HEAD fixes the future without invalidating every SHA behind you. Filter-branch rewrites are slower, riskier, and unnecessary when the index converges going forward.
Mixed Endings Breaking Diffs and Builds
Mixed endings hurt in two places: human review and machine execution. A file that flips from LF to CRLF shows every line as changed in git diff, burying the one logic line under thousands of ending-only lines. Reviewers either wave it through blind or demand a cleanup that delays the feature. Execution breaks harder: bash reads the trailing \r as part of the shebang and command names, Python may tolerate it but shebang lines don't, and old Windows batch tools choke on bare LF.
Detect both failure modes with the same toolkit. git diff --stat flags whole-file rewrites from single-line edits — that shape screams endings. git diff | cat -A shows ^M on lines you never touched. For builds, file names the type (with CRLF line terminators is the smoking gun) and grep -c $'\r' counts the carriage returns. ls-files --eol maps the blast radius across the repo.
Fix the instance, then the class. Convert the broken file with the attribute plus renormalize flow, not with a manual editor save that may flip it back next week. Then pin its pattern in .gitattributes and add a CI gate: git grep -I --files-with-matches $'\r' -- '*.sh' fails the build when CRLF sneaks into scripts. Diffs get readable again because only real changes differ.
Treat phantom diffs as bugs, not noise. Every whole-file diff you excuse trains the team to skim, and skimmed reviews miss the logic error hiding under the endings.
Editors and EditorConfig: Stop the Next Mix
Attributes normalize on commit, but editors decide what hits the disk between commits. An IDE set to CRLF on Windows will keep producing CRLF working files that look dirty against an LF index, re-raising warnings developer by developer. EditorConfig closes the loop: a committed .editorconfig with end_of_line = lf tells every compliant editor to save LF, and it travels with the repo just like .gitattributes.
Set both layers to agree. Keep .gitattributes as the enforcer ( text=auto, .sh eol=lf) and .editorconfig as the producer (end_of_line = lf, insert_final_newline = true, charset = utf-8). Configure the big three editors once: VS Code with files.eol set to , IntelliJ with Line separator set to Unix, and Vim with fileformat=unix. Document the trio in onboarding so new hires converge on day one.
Verify the loop holds. After saving, git diff should show only your logic lines, git ls-files --eol should show w/lf for your files, and git status should stay quiet on untouched files. If warnings return for one developer, check their editor setting before touching shared config — the repo is likely right and their save style is wrong.
Prevention beats renormalization. One EditorConfig commit plus a pre-commit hook that rejects CRLF in scripts costs minutes and ends the class of incident this guide opened with.
The CRLF Deploy That Broke 14 Cron Scripts at Midnight
- Shell scripts must be pinned to LF in .gitattributes. Relying on each developer's OS setting guarantees a CRLF commit eventually reaches Linux.
- Warnings are contracts waiting to be written: the first LF/CRLF warning should trigger a .gitattributes commit, not a shrug.
- Gate endings in CI with a grep for carriage returns in scripts. A one-line check catches what code review eyes can't see.
| File | Command / Code | Purpose |
|---|---|---|
| git ls-files --eol | head -30 | Reading the Warning | |
| git config --global core.autocrlf true | core.autocrlf True, Input, False | |
| cat .gitattributes | .gitattributes Text and EOL | |
| git status --short # commit or stash first | Renormalize the Repo | |
| git diff --stat | Mixed Endings Breaking Diffs and Builds |
Key takeaways
Common mistakes to avoid
5 patternsIgnoring the warning because it's only a warning
Setting core.autocrlf false to silence the message
Editing .gitattributes without renormalizing
Fixing endings with manual editor re-saves
Rewriting history to clean endings
Interview Questions on This Topic
What does warning: LF will be replaced by CRLF mean?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
That's Git. Mark it forged?
6 min read · try the examples if you haven't