[BUG] Claude can't edit files that use tabs for indentation

Open 💬 61 comments Opened Nov 11, 2025 by d5ve

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

For some reason https://github.com/anthropics/claude-code/issues/9163 was closed with no resolution.

This is affecting me. Claude fails to edit files indented with tabs. PHP code in my case. It falls back to running ridiculous and fragile sed commands. See below.

What Should Happen?

The Update tool should be able to make changes to files that use tabs for indentation.

$ file application/views/user/layout/service-note-modal.php
application/views/user/layout/service-note-modal.php: HTML document, ASCII text, with very long lines (318)

Error Messages/Logs

● Read(application/views/user/layout/service-note-modal.php)
  ⎿  Read 25 lines

● Update(application/views/user/layout/service-note-modal.php)
  ⎿  Error editing file

● Bash(sed -i '88,109s/^\t\t\t\t\t\t/\t\t\t\t\t\t\t/' /home/anon/apps/portal/application/views/user/layout/service-note-modal.php && sed -i '109a\\t\t\t\t\t\t</div>' /h…)
  ⎿  Running…

Steps to Reproduce

Have a file with tabs.
Ask for an edit.
Watch Claude flail.

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.0.37 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

iTerm2

Additional Information

_No response_

View original on GitHub ↗

61 Comments

github-actions[bot] · 8 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/9163
  2. https://github.com/anthropics/claude-code/issues/10332
  3. https://github.com/anthropics/claude-code/issues/753

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

d5ve · 8 months ago

It is a duplicate with https://github.com/anthropics/claude-code/issues/9163 BUT that was closed with no resolution. So it's still an active bug.

It is probably a duplicate with https://github.com/anthropics/claude-code/issues/10332 BUT that was closed as a duplicate for the other one. So it's still an active bug.

It is probably a duplicate with https://github.com/anthropics/claude-code/issues/753 BUT that was closed mentioning a fix applied months ago. I am still seeing the issue today so it's still an active bug.

d5ve · 8 months ago

This is ridiculous!

● Update(application/views/user/service-logs.php)
  ⎿  Error editing file

● Let me check the exact whitespace in that section:

● Bash(sed -n '137,157p' /home/anon/apps/portal/application/views/user/service-logs.php | cat -A)
  ⎿  ^I^I^I^I^I<div class="card-body scroll-y overflow-hidden p-0 d-none">$
     ^I^I^I^I^I^I<div class="p-4 p-xl-6 text-editor">$
     ^I^I^I^I^I^I^I<h5>Service notes</h5>$
     … +18 lines (ctrl+o to expand)

● Now I can see the exact whitespace (tabs). Let me update it correctly:

● Update(application/views/user/service-logs.php)
  ⎿  Error editing file

● I see there's a trailing space on one line. Let me match it exactly:

● Update(application/views/user/service-logs.php)
  ⎿  Error editing file

● Let me try a different approach - I'll edit just the content part:

● Update(application/views/user/service-logs.php)
  ⎿  Error editing file

● Let me use Write to rewrite the whole file with the changes:

● Bash(cp /home/anon/apps/portal/application/views/user/service-logs.php /home/anon/apps/portal/application/views/user/service-logs.php.backup)
  ⎿  Running…

 Bash command

   cp /home/anon/apps/portal/application/views/user/service-logs.php /home/anon/apps/portal/application/views/user/service-logs.php.backup
   Backup service-logs.php before editing

 Do you want to proceed?
 ❯ 1. Yes
   2. Yes, and don't ask again for cp commands in /home/anon/apps/portal
   3. Tell Claude what to do differently


● I'll continue by making smaller edits. Let me add the title and date fields to the panel:

● Update(application/views/user/service-logs.php)
  ⎿  File must be read first

● Read(application/views/user/service-logs.php)
  ⎿  Read 25 lines

● Update(application/views/user/service-logs.php)
  ⎿  Error editing file

● Bash(sed -i '139 i\\t\t\t\t\t\t\t<h5>Title</h5>\n\t\t\t\t\t\t\t<p id="serviceLogTitle"></p>\n\t\t\t\t\t\t\t<h5>Date</h5>\n\t\t\t\t\t\t\t<p id="serviceLogDate"></p>'…)
  ⎿  Running…
d5ve · 8 months ago

Check out this command!

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Bash command

   sed -i '337 a\\t\t\t\t\t\t\t\t\tif (response.service_log.vehicle_service_history_title != "" && response.service_log.vehicle_service_history_title !== null) {\n\t\t\t\t\t\t\t\t\t\t$("#serviceLogTitle").html("").html(utf8Decode(response.service_log.vehicle_service_history_title));\n\t\t\t\t\t\t\t\t\t} else
   {\n\t\t\t\t\t\t\t\t\t\t$("#serviceLogTitle").html("").html("N/A");\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (response.service_log.service_date != "" && response.service_log.service_date !== null)
   {\n\t\t\t\t\t\t\t\t\t\t$("#serviceLogDate").html("").html(response.service_log.service_date);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t$("#serviceLogDate").html("").html("N/A");\n\t\t\t\t\t\t\t\t\t}' /home/anon/apps/portal/application/views/user/service-logs.php
   Add title and date population code

 Do you want to proceed?
 ❯ 1. Yes
   2. Yes, and don't ask again for sed commands in /home/anon/apps/portal
   3. Tell Claude what to do differently
GlassBeaver · 7 months ago

I'm facing similar problems. If it's of any help, I asked claude to tell me what the problem is:

can you tell me what was causing your edits to fail and what I can do to fix it

To which it replied:

The issue was with how the Edit tool matches text - it requires exact whitespace matching, including: 1. Tabs vs Spaces: The codebase uses tabs, but when I copy text from the Read tool output, the line number prefix format obscures the actual indentation. The Read tool shows 340→ with the arrow, and everything after the tab following the arrow is the actual file content. I was mixing tabs and spaces in my search strings. 2. Line endings: Windows uses CRLF (\r\n), not LF (\n). When I construct multi-line strings in the tool parameters, they default to LF unless I'm careful. What eventually worked: - Reading the file first (required by Edit tool) - Using smaller, unique snippets to match (less whitespace to get wrong) - Matching the indentation exactly as shown AFTER the line number arrow in Read output - The Edit tool automatically handles CRLF correctly when it reads/writes, so I didn't need to worry about that once I got the tabs right What you could improve (if you control the tooling): 1. Make the Edit tool more lenient with whitespace matching (normalize tabs/spaces automatically) 2. Show the actual whitespace characters in Read output (like ^I for tab, $ for line end) 3. Add a parameter to Edit like normalize_whitespace: true that ignores differences in indentation 4. Better error messages - instead of just "String to replace not found", show what character position first differed For now, the workaround is to use very small, unique code snippets when editing, so there's less whitespace to match exactly.

I'm on Windows 11, in my case it's c++ and I'm using tabs instead of spaces for indentation. Line endings on Windows are CRLF and Claude mentions that its tooling was designed for LF so that's another problem.

mj321 · 7 months ago

Same here, using WSL/Windows 11. This is a serious issue for people who are affected.

tskimmett · 7 months ago

This is still a problem and a massive time/token waster. Virtually every time I kick off an implementation after planning I'm hit with the dreaded Error editing file, followed by 2 minutes of claude trying to figure out how to insert a few lines of code in a file.

d5ve · 7 months ago

I still run into this issue.

I have had some success with periodically adding the following to the chat.

This file uses tabs for indentation. Use Update() or Write() with tab support. You are forbidden to use Bash()/sed/awk/etc.

In most cases, the next few file edits do work.

pdb0102 · 6 months ago

I ran into the same problem and I added the following instruction to CLAUDE.md, which taught Claude how to not run into the issue:

## Edit Tool - Whitespace Handling

  The Read tool uses `→` to mark where line numbers end and file content begins.

  **Rule:** Copy the EXACT whitespace that appears after the `→` marker.
  - Whatever appears between `→` and the code text is what's actually in the file
  - That whitespace must be used EXACTLY in Edit tool's old_string
  - Don't count arrows, don't interpret - just copy what's after the `→`

  **Example:**
  14→		private byte tag;
  For Edit, use: `		private byte tag;` (copy everything after →, including the two tabs)

  **If Edit fails:** Stop and explain the problem. Do not attempt sed/awk/bash workarounds.

  **IMPORTANT**: Trust the Read tool output. Copy what's after `→` into Edit immediately. DO NOT verify with sed/od/grep first - that's wasting time and the instructions already tell you to stop if Edit fails, not to pre-verify.
 ```
github-actions[bot] · 5 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

vargist · 5 months ago

The issue is still occurring.

mjasnikovs · 5 months ago
● Update(client/src/components/orders/OrdersCalendar.tsx)
  ⎿  Error editing file

● Read 1 file (ctrl+o to expand)

● Update(client/src/components/orders/OrdersCalendar.tsx)
  ⎿  Error editing file

● Read 1 file (ctrl+o to expand)

● The file uses tabs. Let me use the exact content from the file.

Extremely annoying. Anybody knows how to fix it?

ignaciogiri · 5 months ago

Any idea what's the fix?

mikej96 · 5 months ago

Has anyone been able to fix this? From Claude:

 The root cause: When I see Read output, I mentally convert tabs to spaces when constructing Edit strings, causing match failures.

I'm not going to sit here and approve every sed command. And just now Claude said the edit was too complex for sed, and it needed to write a perl script to apply the edits.... A PERL SCRIPT TO EDIT A FILE.

Codex does not have this problem.

rivertwilight · 4 months ago

This happens everyday. I'm not paying for tokens to fix the failed apply due to indent mismatch. Please fix this.

d3lay · 4 months ago

I (probably) have the same issue. Quote from Claude:

- Edit tool fails on backtick template literals with tab indentation — use Bash with node -e for those edits
jezweb · 4 months ago

Still hitting this regularly on macOS (darwin 25.2.0, Opus 4.6). The model narrates its workaround strategy to the user — "The Read tool shows them as spaces, but Edit needs actual tabs. Let me use a simpler match." — burning context window and user attention on every retry.

Happens multiple times per session when editing tab-indented files (PHP, Makefiles, Go, etc.). The model eventually succeeds but wastes 2-3 attempts each time.

eg from terminal:

`⏺ Bash(cat -vet /Users/jez/Documents/...... |
sed -n '276,291p')
⎿  ^I^I^I^Iif (bindings.CONTENT_INDEX) {$
^I^I^I^I^Itry {$
^I^I^I^I^I^Iconst upsertedPage = await getPageByUrl(bindings.DB, pageUrl);$
… +13 lines (ctrl+o to expand)

⏺ 6 levels of tab indentation. I need to write the replacement with matching tabs. Let me write the
whole block using a Write approach — actually, let me use a single targeted edit with the exact
unique string.

⏺ Read 1 file (ctrl+o to expand)

⏺ The issue is that Edit is converting my tabs to spaces. Let me use Bash with sed for this specific
edit since the tab handling is problematic.
`

xDreamCoding · 4 months ago

Also having this issue on Windows. Will this ever be acknowledged?

alphonseb · 4 months ago

Ran into this issue multiple times as well, cf Claude reasoning:

Now I can see the exact content. The indentation is tabs (\t), and the alignment padding after field names uses spaces. I can see:
  - Line 165: 5 tabs + ...
  - Line 166: 6 tabs + ...
  - etc.

  The Edit tool might be converting tabs to spaces in the old_string parameter. This is probably a known issue. Let me try using sed in Bash or the Write tool to replace the entire file.

  Actually, let me try the Edit tool one more time with very explicit tab characters. The issue could also be that when I copy from the Read output, the Read tool uses a format of spaces + line_number + tab +
  content, and the tab character after the line number might be confusing things. But the content itself should be preserved correctly.

  I think the Edit tool is converting my tabs to spaces when I input them. I'll just write out the whole file instead.

Then it used a combination of Read + Write tools to completely overwrite the file.

shmert · 4 months ago

This appears to be something that won't be fixed? Rather disappointing

jerodvenemafm · 4 months ago

Agreed that this is super annoying. I've got to sit here and watch this thing create a python script or exec sed just to edit a file, approving each command? Come on...

hbmartin · 4 months ago

At cc's suggestion, I tried adding this to my CLAUDE.md but it doesnt help much
- If the Edit tool fails with "String not found" on tab-indented files, try a shorter or differently-bounded match string (e.g., spanning lines with mixed indentation levels, or starting from a more unique position) before reaching for any shell tool.

shmert · 4 months ago

This might have helped:

When you read a file, your reader converts tabs to spaces. You can assume that any leading whitespace has been converted, and four spaces represents one tab.
heavygweit · 4 months ago

Also experiencing this issue - only started in the last week - running on MacOS Tahoe.

Adding notes/instructions in claude.md and memory.md have zero effect, I have to prompt several times within a session that the file uses tabs. Experiencing the same sequence of an error reading/editing the file, then falling back to sed to replace text.

Huge time suck + token waste to sit there manually approving sed commands (which fail half the time) or cancelling whole plans/edit sequences to restart with yet another reminder that the file uses tabs.

Claude itself:

My default assumption should be tabs — the CLAUDE.md for this project explicitly says "Tabs for indentation." I should be constructing old_string with tabs by default on this codebase.   As for why it's suddenly happening: I honestly don't know. I've successfully used the Edit tool on this project before. Something in my recent behavior is causing me to construct old_string with spaces despite knowing the project uses tabs — whether that's a model behavior shift, me being sloppy when typing out multi-line old_string values, or something about how deeply-nested JSX looks when I'm constructing it. I can't give you a definitive answer.
mabar · 4 months ago

Since it seems unlikely to be fixed any time soon, I've researched our options.
Edit tool built-in alternative that shouldn't fail is Write tool, but it is token-heavy, it rewrites the whole file.
Sed is more efficient, but we have to manually approve it, Claude doesn't prompt to pre-approve it.
But we can pre-approve it by adding sed to the config

{
  "permissions": {
    "allow": [
      "Bash(sed *)"
    ]
  }

And save instructions to memory, so it doesn't repeatedly waste time on Edit tool or try to use node or python instead of sed

 ## Edit Tool Fallback

  The Edit tool fails on files combining tab indentation with backtick template literals (`.js`, `.ts`, `.latte` files). When Edit fails on these files:

  1. Do not retry Edit — fall back to `Bash` with `sed`
  2. If `sed` is not pre-approved, suggest adding `Bash(sed *)` to `.claude/settings.local.json` under `permissions.allow`
pokono · 4 months ago

Same here, same here. I'm almost giving up and using PHPStorm to commit the code as it auto format the code before committing. I know most people uses spaces nowadays but I find it really sad to have a whole key on the keyboard made specifically for tabulation and having to revert to spaces for the sake of AI.
Please, from dev to dev, help us!

hbmartin · 4 months ago

I asked Claude for extensive debugging and this was how it summarized its results:

⏺ To summarize the actual root cause:

  The Edit tool is fine. It does exact byte matching, which is correct behavior. The failure is that I can't
  reliably distinguish \t from visually-identical spaces in the Read tool's output, especially when counting
  multiple levels of nesting or when indentation inside template literals might use a different style than the
  surrounding code. The "backtick template literals" pattern was a real correlation — template literal content is
  where whitespace is most ambiguous — but it's my whitespace generation that fails, not the tool.

@chrislloyd @stevenpetryk @hackyon-anthropic @ashwin-ant @fvolcic @ant-kurt @bogini @OctavianGuzu @whyuan-cc @ThariqS @ddworken @dicksontsai @catherinewu

robsonferr · 4 months ago

Same here. The strange thing is that this started this week. I had never seen Opus 4.5 do this, but it seems that 4.6 gets very confused with tabs. In my scenario, it's HTML and Typescript files. It even creates a Python script to try to inject the tabs. Insane.

mjasnikovs · 4 months ago

Pleas fix this stupid bug. Took like 5 minutes to fix tabs! And in the end, i had to fix it manually!

```● Update(src/routing/pdf/orderLinesReportPdf.ts)
⎿  Error editing file

● Bash(cat -A /home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts | sed -n '1408,1415p')
⎿  ^I^I^I^I^I^I]$
^I^I^I^I^I]$
^I^I^I^I}$
… +5 lines (ctrl+o to expand)

● Update(src/routing/pdf/orderLinesReportPdf.ts)
⎿  Error editing file

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  '\t\t\t\t\t]\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst pdfDoc'
Found: True
"\n\t\t\t\t\t\t\t{text: totalWbSkaits, style: 'cellBold', fillColor: 'silver'}\n\t\t\t\t\t\t]\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst pdfDoc = pdf"

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Done

● Let me verify the result looks correct:

● Searched for 1 pattern, read 1 file (ctrl+o to expand)

● Bash(cat -A /home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts | sed -n '1300,1310p')
⎿  ^I^I^I^I^I^I^Ireturn rows$
^I^I^I^I^I^I})$
^I^I^I^I^I] as TableCell[][]$
… +8 lines (ctrl+o to expand)

● Searched for 1 pattern (ctrl+o to expand)

● The new else if is at 1 tab instead of 2. Let me fix the indentation:

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Found: True
Found2: True
Done

● Now add DestCountryCodeFin to the landscape condition:

● Read 1 file (ctrl+o to expand)

● Update(src/routing/pdf/orderLinesReportPdf.ts)
⎿  Error editing file

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Found: False
'= OrderLinesReportType.OakWb) {\n\t\t\tconst mult = (line: OrderLineModel) => {\n\t\t\t\tif'

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Line 1488: '\t\t\t\t\t\targs.data.report === OrderLinesReportType.OakWb\n'

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Line 1486: '\t\t\t\t\t\t) &&\n'
Line 1487: '\t\t\t\t\t\t\targs.data.reportMode === OrderLinesReportMode.Full) ||\n'
Line 1488: '\t\t\t\t\t\targs.data.report === OrderLinesReportType.OakWb\n'
… +7 lines (ctrl+o to expand)

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Found: True
Done

● Now let me verify the final state of the key sections:

● Read 1 file (ctrl+o to expand)

● Bash(cat -A /home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts | sed -n '1408,1420p')
⎿  ^I^I^I^I^I^I]$
^I^I^I^I^I]$
^I^I^I^I}$
… +10 lines (ctrl+o to expand)

● The body inside the new else if is under-indented by one tab. Let me fix:

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Block body: lines 1414 to 1461 (0-indexed)
First body line: '\t\tconst orderLines = await orderLineReportDestCountryCodeFin(args.data, context.query)\n'
Last body line: '\t\t}\n'
Done

● Bash(cat -A /home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts | sed -n '1412,1465p')
⎿  ^I^I}$
^I^I} else if (args.data.report === OrderLinesReportType.DestCountryCodeFin) {$
^I^I^Iconst orderLines = await orderLineReportDestCountryCodeFin(args.data, context.query)$
… +35 lines (ctrl+o to expand)

● There's one extra \t\t} — the content = {} closing is at 2 tabs but should be 3. Let me check and fix:

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  1458: "\t\t\t\t\t\t\t{text: line.qtyOrigOrd, style: 'cell'}\n"
1459: '\t\t\t\t\t\t])\n'
1460: '\t\t\t\t\t]\n'
… +12 lines (ctrl+o to expand)

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Line 1462: '\t\t}\n'
Fixed to: '\t\t\t}\n'
Done

● Now let me also check there's an empty line issue (the blank line with just \t tab):

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  1413: '\t\t} else if (args.data.report === OrderLinesReportType.DestCountryCodeFin) {\n'
1414: '\t\t\tconst orderLines = await orderLineReportDestCountryCodeFin(args.data, context.query)\n'
1415: "\t\t\tif (orderLines.length === 0) return res.status(404).json(new UserInputNotExistError('orderLineIds'))\n"
… +5 lines (ctrl+o to expand)

● Line 1416 has a \t\n (tab + newline) instead of just \n. Let me clean that up and do a final verification:

● Bash(python3 - << 'PYEOF'
with open('/home/edgars/hub/aiz-server/src/routing/pdf/orderLinesReportPdf.ts', 'r') as f:…)
⎿  Done

● Now let me do a final read to verify the complete block looks correct:```

xDreamCoding · 4 months ago
Pleas fix this stupid bug. Took like 5 minutes to fix tabs! And in the end, i had to fix it manually!

Basically AGI

cuonghuunguyen · 4 months ago

how can it happen in the best coding agent tool? LMAO

jmtimko5 · 4 months ago

happening for me as well

fraenkDUS · 4 months ago

Same problem here with Sonnet 4.6/Claude Agent SDK through GitHub Copilot in VS Code and Biome/tab by default formatted TypeScript files in Windows. No problem for Opus 4.6 so far.

mikej96 · 4 months ago

Same problem on my end with the latest version of claude.

mrclrchtr · 4 months ago

Lately, I've often wondered why Claude uses sed so much... but then I just kind of ignored it.

I was about to buy a Max plan today, but when Claude wrote a huge Python script just to make a small update, I started to wonder what was going on and found this issue.

It's unbelievable that this problem even exists.

PeterN · 4 months ago

Have you tried writing your code yourself?

WithHolm · 4 months ago

for a fix:
ive created a small tool that converts indentation to spaces before it reaches claude and back if file is tab indented.. so essentially claude always reads spaces but the tool does the actual conversion:
you add it as a pre-edit hook, and it works really well :)

https://github.com/WithHolm/claude-tab-fix

jezweb · 4 months ago

ive not noticed this happening anymore, or if it does maybe its much less frequent so one of the recent fixes for new lines etc maybe helped?

hbmartin · 4 months ago

Oh well, at least 5.3-codex or 5.4 on extra-high consistently produce notably higher quality than CC, even if they are rather slow.

Guess I'll just use CC for code review.Nevermind! Seems reviews also do not actually work.

@amorriscode @rboyce-ant @dhollman @sid374 @Masa1984a @amorriscode

WithHolm · 4 months ago
ive not noticed this happening anymore, or if it does maybe its much less frequent so one of the recent fixes for new lines etc maybe helped?

I've seen that it can handle "simple" updates, but if it comes to code that is edited along multiple nestings it struggles some days.. one day it will work as expected, but other days I wonder if it eats crayons

Amnesiac9 · 3 months ago

Still an issue

michael-odell · 3 months ago

Still an issue for me with Sonnet 4.6 on macos: 2.1.81 (Claude Code).

h4xxr · 3 months ago

Hitting this constantly as a WSL2 user with source code on an NTFS mount.

My codebase is Visual Studio C# — true Windows projects that can't be put through Unix normalisation.

The visible symptom is a stream of "error editing" failures followed by silent fallbacks to Python or sed to perform the edit instead. My biggest frustration with this workaround is that it bypasses the diff UI entirely — I struggle to review what actually changed before accepting it, which defeats much of the point of using Claude Code interactively.

I haven't dug into the internals myself, but the frequency of failures on my setup makes me suspect that NTFS mounts may compound the tab-matching issue further.

dannguyen · 3 months ago

Still having this issue when handling typescript and svelte files

yurukusa · 3 months ago

/tmp/issue-11447-answer.md

abgros · 3 months ago

I can confirm that https://github.com/anthropics/claude-code/issues/11447#issuecomment-3694300590 solves the problem. Maybe it should be added to the system prompt?

hbmartin · 3 months ago

Root Cause

The failure stems from a specific design choice in the compact line prefix format used by the Read tool (FileReadTool).

The Ambiguous Separator

addLineNumbers in src/utils/file.ts has two modes. In the default compact mode (enabled by isCompactLinePrefixEnabled()), each line is formatted as:

N<TAB><line content>

The separator between the line number and the file content is a literal \t (tab character). This is the root of the ambiguity. If the actual file uses tab-based indentation (e.g., \t\tfunction foo()), the Read tool output becomes:

42   \t  \t  \t  function foo()
^         ^^^^^^^^^
separator  actual file tabs

All three \t characters are visually identical. The model cannot distinguish the line-number delimiter from the file's own indentation. This problem compounds at multiple nesting levels and is worst inside template literals, where indentation may intentionally mix styles.

Why convertLeadingTabsToSpaces Doesn't Help Here

The function convertLeadingTabsToSpaces exists in src/utils/file.ts:

But it is not applied in the FileReadTool's formatting path. formatFileLines in FileReadTool.ts calls addLineNumbers directly on raw file content:

convertLeadingTabsToSpaces is only used in FileEditTool/utils.ts for the display diff (not for the model-facing content), and in the terminal UI renderer (HighlightedCode/Fallback.tsx) — never in what the model sees when reading a file. [4](#0-3)

---

Proposed Fixes

There are two viable approaches:

---

Fix 1 (Surgical): Change the Compact Separator from \t to (U+2192)

The minimum-change fix is to replace the tab separator with the arrow character (U+2192), which is already used in the legacy format and is unambiguously non-whitespace. This is a one-line change in addLineNumbers:

Change \t in the template literal to \u2192. This works because:

  1. stripLineNumberPrefix already handles both and \t via the character class [\u2192\t] — no change needed there: [6](#0-5)
  1. The queryHelpers.ts downstream stripping logic also uses stripLineNumberPrefix, so it also requires no change: [7](#0-6)
  1. The prompt description in FileEditTool/prompt.ts would need a minor text update (changing 'line number + tab' to describe the separator): [8](#0-7)

This fix imposes no risk to FileEditTool matching because old_string is always matched against the raw file content (not the Read tool's formatted output). The normalizeFileEditInput function in FileEditTool/utils.ts reads the actual file for comparison, independent of the separator character: [9](#0-8)

---

Fix 2 (Deeper): Apply convertLeadingTabsToSpaces Inside formatFileLines

A more thorough fix normalizes leading tabs before adding line numbers in FileReadTool.ts:

Change formatFileLines to call convertLeadingTabsToSpaces(file.content) before passing to addLineNumbers. This way the model always sees (spaces) for indentation, never \t, and the \t separator after the line number remains the only tab on each line.

However, this fix requires a corresponding change to normalizeFileEditInput in FileEditTool/utils.ts. Currently, when the model copies old_string from the Read output it sees space-indented content, but the actual file has tabs — so the exact-match check will fail: [10](#0-9)

A new fallback would need to be added after the desanitize fallback: normalize leading tabs to spaces in both fileContent and old_string to find the match location, then reconstruct the actual (tab-containing) old_string for the edit application.

hbmartin · 3 months ago

Apparently the above behavior is entirely controlled by a backend experiment configuration which explains the inconsistency we're seeing.
And makes it all the more maddening that there is no config option here since conditional path is literally already implemented.

dannguyen · 3 months ago

Claude Code leaking its source code may be the best thing this year to happen to Claude Code

LbISS · 3 months ago

I remember 20 years ago saying to a colleague, when he talked about "keeping spaces for compatibility": Hey, we’re past the dark times already — modern tools work fine with semantic things like tabs.
And here we are, 20 years later, in 2026 — damn, AI still cannot work with tabs. What’s next? Will it break files without a newline at the end? Or will we have to add a carriage return manually after each line AI writes?
It's ridicolous the issue is staying open for half a year.

tuomastanner · 3 months ago

Seems silly, but I was able to fix this issue with one line addition to APPEND_SYSTEM.md

  • ALWAYS remove @ prefix from filepaths when using tools. ALWAYS differentiate between TABS and SPACES so edit tool works.
fwsGonzo · 3 months ago

Can we get this known problem with a known fix applied? It's making VSCode claude code impossible to use

WithHolm · 3 months ago
Can we get this known problem with a known fix applied? It's making VSCode claude code impossible to use

i posted a plugin i made earlier that fixes this. after a lot of different reiterations, i ended up both trying to auto-indent any posted code using the file known indentation AND instruct claude preciely of how to read the output from its read functionality each time it reads as a context clue. using it on a go project that mixes both tabs and spaces, it works almost flawlessly now. i haven't had any issues in the last couple weeks: https://github.com/WithHolm/claude-tab-fix you can download the build exe, build it from go source and install the plugin to get the correct hooks

SevenSystems · 2 months ago

I can't believe someone at the most valuable AI company on planet earth has made an objectively ludicrous decision such as using the most important and prevalent character in code -- tab -- as a crucial delimiter in a toolchain that deals with... code. In a day and age where indentation-sensitive languages are extremely prevalent (Python). How is this even possible?

mrekin · 2 months ago

For me
v2.1.81 works fine (not perfect) with Edit, but latest v2.1.1xx - better to use sed command not Edit tool..

RnMss · 2 months ago

It's been 6 months, can't believe this bugs still exists.

78Spinoza · 2 months ago

WTF Is wrong with this company ? This is a no brainer bug that can easily be fixed ? no? why do they not fixing it ? Retardation?

f123888 · 1 month ago

Same Problem. Very Disgusting.

HustCoderHu · 1 month ago

Same Problem, why this basic tool func bug still exists, this problem never appears in codex cli

enricolam · 1 month ago

It's a feature designed to drive token usage, not a bug

wall-69 · 29 days ago

Guys dont worry, AI will take our job, except for code bases with tabs.