[Bug] Edit tool corrupts Unicode typographic characters in non-ASCII text
Bug Description
Subject: Critical Issue - Claude Code Corrupts French Typography (Apostrophes) Even When Copy-Pasting
Problem:
Claude Code systematically corrupts French typographic apostrophes ' (U+2019 RIGHT SINGLE QUOTATION MARK) by replacing them with straight apostrophes ' (U+0027 APOSTROPHE) when using the Edit tool. This happens even when explicitly copy-pasting text from the Read tool.
Critical Detail:
The correct characters are already in the user's files. It's Claude Code that removes them and replaces them with the wrong characters during the Edit operation.
Impact:
- Every single Edit operation on French code corrupts the typography
- Makes code reviews extremely painful - developers must manually check every French string for corruption
- Breaks professional French typography standards (U+2019 is the correct apostrophe in French)
- Slows down development significantly - every commit requires manual verification and correction
- Creates frustration and makes Claude Code unreliable for non-English codebases
Examples:
"Tourner l’image vers la gauche" turn into "Tourner l'image vers la gauche" and that is not desirable.
Attempted Workaround - FAILED:
We tried having Claude:
- Use Read tool to see the original text with correct apostrophes
- Copy-paste the exact text (including French strings) into Edit tool
- Only modify the code logic, not the French strings
Result: The workaround doesn't work. Even when Claude copy-pastes text from Read into Edit, the Edit tool call automatically converts ’ (U+2019) to ' (U+0027) before sending it to the system.
Root Cause:
The Edit tool appears to normalize/sanitize text input, converting Unicode typographic characters to ASCII equivalents. This is destructive for languages that use proper typography.
Reproduction:
- Create a file with French text: altinaeTooltip="Tourner l'image vers la gauche" (note the ' is U+2019)
- Use Read tool - Claude sees the correct apostrophe
- Copy-paste that exact line into Edit tool to change nearby code: [disabled]="!loaded" → [disabled]="!loaded()"
- Result: The apostrophe in l'image is silently converted from U+2019 to U+0027
Suggested Solutions:
Option 1 (Preferred): Add a setting/flag to preserve Unicode characters in Edit operations:
- preserveUnicode: true - Never normalize typographic characters
- Should be opt-in or per-workspace configurable
Option 2: Detect quoted strings and preserve their exact Unicode characters:
- Don't normalize text inside "..." or '...' strings
- Only normalize code/whitespace
Option 3: Provide character-level diff information to user:
- Show when Edit would change Unicode characters
- Let user approve/reject character normalization
Affected Languages:
This likely affects all languages with proper typography:
- French: ' (apostrophe), «» (guillemets)
Not tested :
- Spanish: ¿¡
- German: „" (quotes)
- And many others
Current Status:
We've had to document this as "Rule #1" in our codebase guidelines, but Claude Code cannot follow the rule due to this technical limitation. The workaround is impossible to implement.
Frequency: This happens on literally every Edit operation involving French text (dozens of times per day in active development).
Environment Info
- Platform: darwin
- Terminal: iTerm.app
- Version: 2.0.76
- Feedback ID:
14 Comments
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.
This is still happening for me – not just French, but any text with proper curly quotation marks.
In the meantime I made a script to hunt quotes with a --fix option so that any time claude make this mistake (it happens all day long) it calls the script. This is the only workaround I have for now
i now have this in my
CLAUDE.mdlolHere is my script that targets french langage patterns for the apostrophe.
The script searches accross all your codebase, ignoring the gitignored files.
It has a --fix option to auto fix quotes.
Pretty sure you can adapt the script to any language.
i told claude to use the script when he struggles with a quote.
quote-hunter.ts
Adding two failure modes involving escape sequences, beyond the direct Unicode→ASCII downgrade described above:
\uXXXXescape sequences innew_stringare written as literal text:Then Edit with
new_string='She said, \u201cHi there.\u201d'— the file receives the literal characters\u201cand\u201d(6 ASCII chars each), not the Unicode code points. So escape sequences don't provide a workaround.\uXXXXinold_stringdoesn't match actual Unicode:Returns "String to replace not found in file." The escape sequence is treated as literal text, so the model can't match existing Unicode characters via this route either.
Result: Both the direct-Unicode path (downgraded to ASCII) and the escape-sequence path (literal text / no match) fail. There is no route through the Edit tool that preserves typographic quotes.
Mixed-encoding note: When a region is edited, the affected lines get ASCII quotes while untouched lines retain Unicode — leaving the file with inconsistent quote styles, which is arguably worse than uniform corruption.
Context: This affects prose/markdown files, not just code with French strings. Any fiction or documentation workflow that depends on typographic quotes hits this on every edit. Still present in Claude Code 2.1.45 on macOS (Darwin 25.2.0).
Workaround: A pre-Edit hook that detects Unicode in the target file and blocks the Edit tool, plus a drop-in replacement script (
uedit) that reads JSON edits from stdin where\uXXXXescapes are properly decoded by Python'sjson.loads.Gist with both files and setup instructions: https://gist.github.com/thejud/802c31016698f1d78dbcecbde2b5575a
The hook adds a CLAUDE.md instruction telling the agent to use
ueditinstead, so it redirects automatically. Not a fix, but it's been reliable for daily use on a prose-heavy project.Can confirm the fix in v2.1.47 works. Tested with a file containing French characters (é, è, ç, «, »), curly quotes (" " ' '), curly apostrophes ('), and em dashes (—). All survived an Edit round-trip with correct UTF-8 bytes verified via
xxd.Retiring my workaround hook from the gist above — leaving the files in place in case of regression.
I think this is a more general issue with the models or a step applied for cleanup after the raw model output before returned from serverside.
Using the chat UI to ask "What apostrophes exist in unicode?" gets it to list them, with U+2019 replaced with U+0027 (verified by checking codepoint).
Tested both Sonnet 4.6 and Opus 4.6.
I also had this break code before, by changing a
.replace("[U+2019]", "[U+0027]")to.replace("[U+0027]", "[U+0027]"), making it a nop.Your description is spot on — the correct characters are already in the files, and Claude Code’s Edit operation actively destroys them. This is especially painful for French (every apostrophe is affected) but impacts any language using typographic marks.
I’ve filed a feature request proposing a
/curlyoption: instead of blind normalization, a lightweight agent verifies whether each curly character is a code delimiter (fix it) or textual content (preserve it). This would solve both the original bug (#29786, curly in code) and the reverse problem you describe.→ [FEATURE] /curly option: context-aware curly/straight character handling in Write/Edit — #31482
I don't think your feature request makes sense as this isn't a claude code bug, but a more general claude one. I don't think such a toggle would make sense either.
(also, please consider writing your own comments)
Incredible, a human!
No, my proposal isn't just a button, but an agent (AI or otherwise) that can be activated, specialized in detecting curved characters, and therefore capable of correcting Claude Code.
Furthermore, I offer explanations that I wish I had received from Anthropic, and I also offer skills for those who are interested.
Regarding the automation of my messages by AI, this stems from the numerous issues I've opened as a human, to which the only response I've received is a bot that automatically closes the conversations.
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.