Playbook: Fix Missing .gitattributes LF Enforcement

When to use

  • repo-consistency-audit.md reports .gitattributes MISSING or no eol=lf rule
  • SOPS decrypt fails with extra text: "\x0d" (CRLF in encrypted file)
  • Shell scripts on Linux servers fail with bad interpreter: /bin/bash^M
  • Windows developer commits a .sh or .env.sops file with CRLF line endings

Root cause

Windows git defaults to CRLF line endings. Without .gitattributes:

  • .sh files committed on Windows get CRLF → fail on Linux servers (^M in shebang)
  • *.env.sops files committed on Windows get CRLF → SOPS timestamp parse fails
  • .github/workflows/*.yml committed on Windows → GitHub Actions may reject with YAML errors

.gitattributes overrides the core.autocrlf setting per file pattern and normalizes line endings at commit time, regardless of the developer’s OS.

Standard .gitattributes entries (all repos)

# Normalize line endings on commit
* text=auto
 
# Force LF for shell scripts — CRLF breaks shebang on Linux
*.sh text eol=lf
 
# Force LF for YAML (GitHub Actions, Docker Compose)
*.yml text eol=lf
*.yaml text eol=lf

Additional entries for repos with SOPS files

# SOPS encrypted secrets — text/YAML format, enforce LF
secrets/*.env.sops text eol=lf

Add this to: p24-infra, radekkonarski-personal-brand (already have it). Also add to any other repo that stores .env.sops files.


Fix

Step 1 — Create or update .gitattributes

$repo = "C:\code_2026\<repo>"
$ga   = "$repo\.gitattributes"
 
# Build the block to add
$block = @"
 
# Normalize line endings on commit
* text=auto
 
# Force LF for shell scripts — CRLF breaks shebang on Linux
*.sh text eol=lf
 
# Force LF for YAML (GitHub Actions, Docker Compose)
*.yml text eol=lf
*.yaml text eol=lf
"@
 
# Append to existing file or create new
$existing = if (Test-Path $ga) { Get-Content $ga -Raw } else { "" }
 
# Avoid adding duplicate block
if ($existing -notmatch "eol=lf") {
  [System.IO.File]::WriteAllText($ga, $existing + $block, [System.Text.UTF8Encoding]::new($false))
  Write-Host "Updated .gitattributes"
} else {
  Write-Host "eol=lf already present — no change needed"
}

Step 2 — Re-normalize already-committed files

After adding .gitattributes, files already in the index retain their old line endings. Run renormalization:

git -C "C:\code_2026\<repo>" add --renormalize .
git -C "C:\code_2026\<repo>" status
# Review what changed — usually only .sh / .yml files

Step 3 — Commit

git -C "C:\code_2026\<repo>" add .gitattributes
git -C "C:\code_2026\<repo>" add --renormalize .
git -C "C:\code_2026\<repo>" commit -m "chore: add .gitattributes — enforce LF for sh/yml/sops files
 
Prevents CRLF corruption on Windows commits for shell scripts and YAML files.
 
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
git -C "C:\code_2026\<repo>" push origin HEAD

Affected repos (as of 2026-06-28)

RepoHas .gitattributesHas LF ruleAction
p24-infranone
et-operational-platformadd standard block
Art-Agencyadd standard block
brandpilotadd standard block
whatsup-android-chat-pulleradd standard block
radekkonarski-personal-brandnone
amazon-kdp-tangoadd standard block

Escalation

If renormalize creates a very large diff (many files converted): commit the .gitattributes first in one commit, then do a follow-up --renormalize commit clearly labelled chore: normalize line endings. This keeps the renormalization separate from code changes for easier git blame navigation.

If a .sh file still fails on Linux after this fix: the file may have been committed before the .gitattributes was pushed. The collaborator must run git pull to get the attributes, then git checkout -- <file> to re-checkout with correct endings.