Skip to content

Bug #23: CRLF/LF Mismatch in Edit Risk Assessment

Status: RESOLVED in v4.0.2 Category: Correctness / Line Endings Severity: High (edits incorrectly blocked on Windows files) Resolution Date: 2026-03-16

When a file on disk uses CRLF (\r\n) line endings and Claude Desktop sends old_text with LF (\n), the risk assessment reports 0 matches and flags the edit as a full rewrite (CRITICAL risk), even though the actual edit engine would find and apply the match correctly.

  1. edit_file on a CRLF file triggers false CRITICAL risk warning
  2. multi_edit reports 0 matches for valid old_text
  3. User must add force: true to bypass the false warning
  4. Risk assessment and actual edit behavior disagree
File content (hex): 6C696E6531 0D0A 6C696E6532 0D0A
l i n e 1 \r\n l i n e 2 \r\n
old_text from Claude: "line2\n" (LF only)
Risk assessment: strings.Count("line1\r\nline2\r\n", "line2\n") = 0 matches
→ CRITICAL: full rewrite detected
Actual edit engine: normalizeLineEndings() converts both to LF
→ strings.Count("line1\nline2\n", "line2\n") = 1 match
→ Edit applies correctly

normalizeLineEndings() (which converts \r\n\n and \r\n) already existed and was called inside performIntelligentEdit(), but was NOT called before:

  1. CalculateChangeImpact() in EditFile() (impact_analyzer.go)
  2. strings.Count() in streamingEditLargeFile() (streaming_operations.go)

The risk assessment ran on raw CRLF content with LF search text, always getting 0 matches.

Added normalizeLineEndings() at the entry point of CalculateChangeImpact():

func CalculateChangeImpact(content, oldText, newText string, thresholds RiskThresholds) *ChangeImpact {
// Normalize line endings so CRLF files match LF search text (Bug #23)
content = normalizeLineEndings(content)
oldText = normalizeLineEndings(oldText)
newText = normalizeLineEndings(newText)
impact := &ChangeImpact{...}

Also added normalization in streamingEditLargeFile() before its own strings.Count() call.

Why at CalculateChangeImpact()? All callers (EditFile, MultiEdit simulation, streamingEditLargeFile) get the fix automatically without modifying each call site.

FileChange
core/impact_analyzer.go3 normalization lines at top of CalculateChangeImpact()
core/streaming_operations.goNormalization before strings.Count() in streamingEditLargeFile()
tests/bug23_test.go4 regression tests
Terminal window
go test ./tests/ -run TestBug23 -v
  1. TestBug23_EditFile_CRLFMatchesLF — CRLF file edited with LF old_text succeeds
  2. TestBug23_EditFile_CRLFRiskNotCritical — Small edit in CRLF file is not flagged CRITICAL
  3. TestBug23_MultiEdit_CRLFMatchesLF — multi_edit on CRLF files works
  4. TestBug23_PureLF_StillWorks — LF files continue working (no regression)