Edit tool fails to match strings in tab-indented files
Problem
When working with files that use tabs for indentation, the Edit tool's old_string parameter frequently fails to find matches, even when the string appears to be correct based on Read output. This forces falling back to Python/Bash for file edits, which defeats the purpose of the dedicated Edit tool.
Reproduction
A file using tab indentation (verified with cat -A showing ^I characters):
^I^I^I<PageTransition>$
^I^I^I^I<div$
^I^I^I^I^Iaria-live="polite"$
The Read tool displays this as:
248→ <PageTransition>
249→ <div
250→ aria-live="polite"
I then attempted an Edit call with old_string containing the text as shown in the Read output (a multi-line block spanning ~50 lines). The edit failed:
String to replace not found in file.
I verified the content existed using a Python script (if old in content: print("FOUND") → FOUND), confirmed the exact tab characters with cat -A, and successfully performed the replacement via Python.
What I observed
- First Edit attempt: Failed on a ~50-line
old_stringthat included tab-indented JSX with inline template literals and ternary expressions - Second Edit attempt (smaller match): Also failed on the same file with a different ~50-line block
- Third Edit attempt (after Python fix): Succeeded on a ~40-line block in the same file with the same indentation style
The inconsistency suggests the failure isn't purely about tabs vs spaces, but may involve specific character sequences within tab-indented content that cause matching to break.
Diagnostic details
- Platform: Linux (Ubuntu-based)
- Model: claude-sonnet-4-6
- File type:
.tsx(React/TypeScript) - Indentation: Tabs (enforced by Biome formatter)
- Indentation depth: 3–5 levels deep (3–5 tab characters)
- Content characteristics: The failing blocks contained JS template literals with
${}interpolation, ternary chains, and inline style objects — all tab-indented
Why I think this happens
The Read tool output format uses cat -n style: 251→<tab>content. The arrow + tab separator is ambiguous when the file content itself begins with tabs. When constructing old_string from Read output, it's easy to get the tab count wrong by ±1 because one tab is consumed by the line-number formatting. The larger the block being matched, the more likely at least one line has a whitespace mismatch.
Suggestions
- Whitespace-tolerant matching mode: An optional flag on Edit that normalizes leading whitespace (tabs↔spaces) during comparison
- Clearer Read output: Use a non-whitespace separator between line numbers and content (e.g.,
251| contentwith a pipe instead of tab) so tab-indented content is unambiguous - Better error diagnostics: When
old_stringfails to match, show the closest near-match with a diff highlighting where the mismatch occurred (e.g., "Line 5: expected 4 tabs, found 3")
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗