Home › DevOps › Git LF CRLF Warning — Fix Line Endings
Beginner 6 min · September 23, 2026

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..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
✓ Production
production tested
September 25, 2026
last updated
1,950
articles · all by Naren
Before you start⏱ 9 min
  • ✓Basic git add, commit, and status workflow
  • ✓A repo cloned on your usual OS
  • ✓A text editor with line-ending settings
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • 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.
✦ Definition~90s read
What is Git LF CRLF Warning Fix?

Line endings are the invisible characters that mark where each text line stops: LF (\n) on macOS and Linux, CRLF (\r\n) on Windows. Git stores text in the repo, typically LF, and may translate on checkout and commit so each OS gets its native style on disk. core.autocrlf sets your personal default for that translation, while .gitattributes sets per-path repo rules that travel with every clone.

★
Think of line endings as line breaks in notebooks.

The warning appears when the two disagree or when no rule exists and Git falls back to guessing.

Four states cover every file. text=auto normalizes detected text to LF on commit and checks out per eol or autocrlf. eol=lf forces LF on disk everywhere — right for scripts. eol=crlf forces CRLF — occasionally right for legacy Windows files. -text or binary disables translation entirely — mandatory for images, archives, and compiled assets that would corrupt if altered.

What it is NOT saves misfires. It's not a merge conflict — conflicts mark content, not endings. It's not file corruption — the text is intact, only break characters translated. It's not fixed by .gitignore — ignored files bypass tracking but tracked files still normalize. And it's not solved by uniform autocrlf alone, because personal config can't pin per-type needs like LF-only scripts.

Think of endings as voltage adapters. The repo runs on one standard, each country's outlets differ, and attributes are the labeled adapters that make every plug fit without daily rewiring.

Plain-English First

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.

BASH
1
2
3
4
5
6
7
8
9
10
# Which files are mixed? index (i/) vs worktree (w/) endings
git ls-files --eol | head -30

# Show invisible carriage returns in the diff
git diff | cat -A | head -40

# Inspect one suspect file directly
file scripts/deploy.sh
grep -c $'\r' scripts/deploy.sh || echo "no CR found"
cat -A scripts/deploy.sh | head -5
📊 Production Insight
Paste git ls-files --eol output into the fix PR. Reviewers can see the before-and-after ending map without trusting invisible characters.
🎯 Key Takeaway
The warning announces a translation, not damage. Use ls-files --eol to see both sides, then fix the policy.

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.

BASH
1
2
3
4
5
6
7
8
9
10
11
12
# Windows: checkout CRLF, commit LF
git config --global core.autocrlf true

# macOS / Linux: normalize to LF on commit
git config --global core.autocrlf input

# Strict no-magic (only with full .gitattributes coverage)
# git config --global core.autocrlf false

# Verify effective value and where it came from
git config --list --show-origin | grep -i autocrlf
git config --show-origin core.autocrlf
📊 Production Insight
Standardize autocrlf in onboarding docs per OS. Teams that leave it at install defaults get a permanent lottery of mixed commits.
🎯 Key Takeaway
Windows uses true, macOS and Linux use input. Keep false only when attributes cover everything.

.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.

BASH
1
2
3
4
5
6
7
8
9
10
11
# Minimal team baseline (.gitattributes)
# * text=auto
# *.sh text eol=lf
# *.bat text eol=crlf
# *.png binary
# *.zip binary

cat .gitattributes
git check-attr -a scripts/deploy.sh
git check-attr -a assets/logo.png
git ls-files --eol | head -20
📊 Production Insight
Review .gitattributes changes like security policy. One -text removal can reintroduce binary corruption or CRLF scripts repo-wide.
🎯 Key Takeaway
Commit * text=auto plus eol pins for executables. Attributes travel; personal config doesn't.

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.

BASH
1
2
3
4
5
6
7
8
9
10
11
# Safe renormalize sequence
 git status --short  # commit or stash first
 git add .gitattributes
git commit -m "Add line-ending rules"

git add --renormalize .
git status --short
git diff --stat

git commit -m "Normalize line endings per .gitattributes"
git ls-files --eol | head -20
📊 Production Insight
Land renormalize commits on Friday-quiet mains with a team notice. Open PRs that rebase afterward stay clean; ones that don't reintroduce CRLF.
🎯 Key Takeaway
Attributes fix the future; renormalize fixes the present. One staged commit converges every clone.

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.

BASH
1
2
3
4
5
6
7
8
9
10
# Spot ending-only whole-file diffs
git diff --stat
git diff -- scripts/deploy.sh | cat -A | head -20

# Prove CRLF in a failing script
file scripts/deploy.sh
grep -c $'\r' scripts/deploy.sh

# CI gate: fail on CRLF in shell scripts
git grep -I --files-with-matches $'\r' -- '*.sh' && echo "CRLF FOUND" || echo "scripts clean"
📊 Production Insight
Whole-file diffs from endings hide real defects in review. Block them in CI so authors renormalize before requesting review.
🎯 Key Takeaway
Phantom diffs bury logic; CRLF breaks shebangs. Prove with cat -A and gate scripts in CI.

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.

💡Make LF the Default Everywhere
Commit .editorconfig with end_of_line = lf and pin your IDE to LF saves. When every editor produces LF, Git has nothing left to translate and the warning retires.
📊 Production Insight
New-hire machines are the top reintroduction vector. A setup script that writes core.autocrlf and checks editor EOL pays for itself in one prevented outage.
🎯 Key Takeaway
Attributes enforce, editors produce. Align both on LF and the warning class disappears.
● Production incidentPOST-MORTEMseverity: high

The CRLF Deploy That Broke 14 Cron Scripts at Midnight

Symptom
Fourteen cron jobs failed simultaneously at midnight with /bin/bash^M: bad interpreter and $'\r': command not found errors. The scripts hadn't changed logically in weeks — git log showed only whitespace. Configs deployed fine, so monitoring blamed the scheduler. Engineers re-ran one script by hand and watched bash choke on invisible carriage returns at the end of every line.
Assumption
The team assumed line endings were an editor cosmetic that Git handled automatically. A new hire on Windows had edited the scripts in an IDE that saved CRLF, committed through a repo with no .gitattributes, and core.autocrlf false meant nothing converted or warned loudly enough to block. Review saw a one-line logic diff and approved; the ending change hid inside it.
Root cause
The repo had no .gitattributes, so no rule forced *.sh to LF. The Windows clone used core.autocrlf true, which converted on checkout but the IDE saved CRLF back and committed CRLF because text wasn't enforced. Linux deploy hosts checked out verbatim CRLF, and bash treats the trailing \r as part of the interpreter path and command names. The midnight cron run was the first execution since the commit, so the breakage waited silently for hours.
Fix
A .gitattributes with text=auto plus .sh text eol=lf and .bat text eol=crlf was committed, then the repo was renormalized with git add --renormalize and one cleanup commit. Developers set core.autocrlf true on Windows and input on macOS and Linux, editors were pinned to LF via .editorconfig, and CI gained a check that fails on CRLF in .sh using git grep. The 14 scripts were redeployed from LF sources and cron went green on the next run.
Key lesson
  • 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.
Production debug guideFive checks that reveal which layer — config, attributes, or editor — mixed your endings.5 entries
Symptom · 01
warning: LF will be replaced by CRLF on git add
→
Fix
Check your OS setting with git config --show-origin core.autocrlf and inspect rules with cat .gitattributes. On Windows expect true, on macOS or Linux expect input. If no .gitattributes exists, add one before changing config, then re-add the file and confirm the warning changes.
Symptom · 02
Shell script fails with ^M or $'\r': command not found
→
Fix
Prove CRLF with file deploy.sh and grep -c $'\r' deploy.sh, or cat -A deploy.sh | head to see ^M markers. Fix the checkout with git add --renormalize deploy.sh after adding *.sh text eol=lf, then verify with file deploy.sh showing ASCII text without CRLF.
Symptom · 03
Whole-file diffs after touching one line
→
Fix
Show the ending change with git diff --stat and git diff | cat -A | head -40 to spot ^M on every line. Run git ls-files --eol to compare index versus working-tree endings per file. Renormalize with git add --renormalize ., commit once, and the phantom diffs collapse.
Symptom · 04
Mixed endings across teammates on different OSes
→
Fix
Audit the repo with git ls-files --eol | head -30 and git config --show-origin core.autocrlf on each OS. Commit a .gitattributes with * text=auto and per-type eol pins, then have each dev run git add --renormalize . so every clone converges to the same index endings.
Symptom · 05
Warning persists after setting autocrlf
→
Fix
Confirm the effective value with git config --list --show-origin | grep -i autocrlf since repo-local config overrides global. Check .gitattributes for a conflicting -text or explicit eol on that path with git check-attr -a <file>. Align the attribute first, then set autocrlf to match your OS.
CRLF Warning Causes Compared
Root CauseHow to ConfirmFixPrevention
core.autocrlf mismatched to OSgit config --show-origin core.autocrlf disagrees with OS defaultSet true on Windows, input on macOS and LinuxDocument per-OS values in onboarding
Missing .gitattributes contractNo .gitattributes file; warnings vary per cloneCommit * text=auto plus eol pins for scriptsRequire attributes in repo template
Stored CRLF needing renormalizationls-files --eol shows mixed index; whole-file diffsgit add --renormalize . and commit onceRenormalize right after adding attributes
Editor saving the wrong endingscat -A shows ^M after fresh saves; only one dev affectedPin IDE to LF plus .editorconfig end_of_line=lfAdd pre-commit CRLF check for scripts
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
git ls-files --eol | head -30Reading the Warning
git config --global core.autocrlf truecore.autocrlf True, Input, False
cat .gitattributes.gitattributes Text and EOL
git status --short # commit or stash firstRenormalize the Repo
git diff --statMixed Endings Breaking Diffs and Builds

Key takeaways

1
The warning announces translation between repo LF and disk endings, not data loss.
2
Set autocrlf true on Windows and input on macOS and Linux.
3
Commit .gitattributes with text=auto and eol pins; it travels, config doesn't.
4
Renormalize once with git add --renormalize after attribute changes.
5
Prove CRLF damage with cat -A, file, and ls-files --eol before fixing.
6
Pin editors to LF and gate scripts in CI so mixed endings can't return.

Common mistakes to avoid

5 patterns
×

Ignoring the warning because it's only a warning

Symptom
Mixed endings accumulate silently until a shell script fails on Linux or a diff becomes unreviewable.
Fix
Treat the first warning as a task: commit .gitattributes, renormalize, and set autocrlf per OS.
×

Setting core.autocrlf false to silence the message

Symptom
Warnings stop but nothing normalizes; CRLF commits flow straight into the shared history on every OS.
Fix
Use true on Windows and input elsewhere. Reserve false for repos with full attribute coverage.
×

Editing .gitattributes without renormalizing

Symptom
New clones behave but existing files keep old endings, so warnings and phantom diffs persist.
Fix
Run git add --renormalize . after every attribute change and commit the resulting normalization.
×

Fixing endings with manual editor re-saves

Symptom
One file flips to LF today and back to CRLF next week because no rule pins it.
Fix
Pin the pattern (like *.sh eol=lf) and renormalize so the fix travels instead of depending on memory.
×

Rewriting history to clean endings

Symptom
Filter-branch invalidates every SHA, breaks open PRs, and forces a coordinated re-clone for a cosmetic fix.
Fix
Normalize once at HEAD with a single commit. Future checkouts converge without rewriting the past.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does warning: LF will be replaced by CRLF mean?
Q02SENIOR
When do you use autocrlf true versus input versus false?
Q03SENIOR
Why does .gitattributes beat core.autocrlf for teams?
Q04JUNIOR
How do you renormalize after adding .gitattributes?
Q05SENIOR
A .sh fails with bad interpreter after a Windows edit. How do you fix an...
Q01 of 05JUNIOR

What does warning: LF will be replaced by CRLF mean?

ANSWER
The file is stored LF in the repo but will check out CRLF on your disk, usually Windows with autocrlf true. Nothing is lost — Git announces the translation. The durable fix is .gitattributes rules plus the right autocrlf per OS.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Is the LF/CRLF warning dangerous?
02
What autocrlf should I use?
03
What should my .gitattributes contain?
04
How do I fix a repo that's already mixed?
05
Why does one file show a whole-file diff?
06
How do I stop editors reintroducing CRLF?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
✓ Verified
production tested
September 25, 2026
last updated
1,950
articles · all by Naren
🔥

That's Git. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
Git Pathspec Did Not Match Fix
53 / 53 · Git