Write/Edit tools silently decode \uXXXX in file content, corrupting escape-sequence text (Linux)
Summary
The Write and Edit tools silently decode \uXXXX sequences in file content as JSON Unicode escapes before writing to disk. As a result, it is impossible to store a literal \uXXXX escape sequence in a file via these tools:
- Authoring the six-character text
\uE010writes the actual U+E010 character. - Authoring
\\uE010to compensate writes a doubled backslash (\\uE010).
Neither produces the intended single-backslash literal \uE010 on disk.
This is the content-corruption analog of several closed Windows-path reports (#58358, #54583, #51815). Those were framed around home-directory paths on Windows and closed as stale/duplicate. This report is about arbitrary file content on Linux, is trivially reproducible, and still occurs, so I am filing fresh per the maintainer note on #54583 ("Please open a new issue if this is still relevant").
Why it matters
Any workflow that legitimately needs literal \uXXXX text in a file is silently corrupted:
- Source code / test fixtures that assert on escape-sequence strings.
- Config or data files that store Unicode escapes as text (e.g. a YAML field holding
\uE010). - Documentation about escape sequences (this very issue body hit the bug while being drafted).
The corruption is invisible: the written characters often render as nothing or as unrelated glyphs, Read shows an apparently empty "", and grep for the intended text cannot find the line. The agent cannot tell it went wrong without dropping to od -c.
Reproducer (Linux)
Call the Write tool so that the file content is the line tengwar: " followed by the six literal characters \uE010, then \uE046, then ". Inspect the raw bytes:
$ od -c file.txt
0000000 t e n g w a r : " 356 200 220 356 201 206
0000020 " \n
356 200 220 = UTF-8 for U+E010, 356 201 206 = U+E046. The escape text was decoded into the actual characters before the file was written.
Attempted workaround — author the content with doubled backslashes (\\uE010\\uE046):
$ od -c file2.txt
0000000 t e n g w a r : " \ \ u E 0 1
0000020 0 \ \ u E 0 4 6 " \n
That yields a doubled backslash, not the intended single-backslash escape. So there is no way to write a literal single-backslash \uXXXX through Write/Edit.
Note this is not clean JSON round-tripping: standard JSON would decode a doubled backslash to a single one, but here the doubled backslash is preserved literally while a bare \uXXXX is decoded. The two behaviors are inconsistent.
Working alternative (for reference)
Writing the same value via Bash with sed/printf produces the correct single-backslash escape on disk (the shell tool's own escape handling collapses the doubled backslash). So the corruption is specific to how \uXXXX in the Write/Edit content argument is decoded at the tool boundary, not an inherent filesystem limitation.
Expected
The content (and Edit new_string) should be written to disk byte-for-byte as authored. A literal single-backslash escape in the intended file content should appear on disk as the six ASCII characters backslash, u, E, 0, 1, 0, not be decoded into U+E010.
Environment
- Platform: Linux
- Tools affected:
Write,Edit(content /new_string). The relatedEditold_stringmatching case is #52813. - Appears to be a transport/argument-decoding issue (JSON Unicode-escape decoding of tool arguments), so likely platform-independent.
Related (but closed)
- #58358 — Write/Edit/Bash decode escapes in paths/heredoc content (closed as duplicate of #54583)
- #54583 — escape in Windows path decoded (closed, not planned, stale; invites a new issue)
- #52813 — Edit silently normalizes escapes in
old_string, breaking exact matches
Related (still open)
- #51815 — Read/Edit fail on Windows username with an escape sequence
- #64479 - Edit tool fails on mixed literal/escape Unicode in multi-line old_string
- #73483 - Write emits raw U+0000 where the model intended the six-character NUL escape sequence
- #66058 - Edit tool corrupts literal escape sequences
🤖 Filed with Claude Code
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Thanks for the detailed repro. Ran it on 2.1.233 (Linux) and reproduced the symptom exactly: asking for the literal six characters
\uE010via Write produced the 3-byte characteree 80 90on disk, and escaping as\\uE010produced a doubled backslash. Same with Edit.However, the raw
stream-jsonoutput shows the Write/Edit tools are not doing the decoding: the string in the tool call had already become the single U+E010 character before the tool ran, and when the tool did receive a two-backslash string it wrote two backslashes to disk byte-for-byte. Tool arguments travel as JSON, and the model is emitting the JSON unicode-escape form ("\uE010"— which is the character) when the intent is literal text, so the six characters never reach the tool.So: confirmed and reproduced on 2.1.233, but the root cause is in how the model encodes its tool arguments, not in the Write/Edit tools themselves — we're routing it accordingly. In the meantime, the reliable workaround is to have Claude write such content via a shell command (e.g.
printf/heredoc), which is what it eventually did in our runs.🤖 Generated with Claude Code
Same problem on MCP tool arguments, on Windows.
Sending a tool argument intended to contain the literal characters
\ u 2 0 1 C:\u201C,\\u201Cand\\\\u201Call arrive at the server as“. I found noescaping form that gets the literal through — doubling does not help at this surface.
Btw,
\\\\u201Cdoes get through in a Bash command, but still decoded to“in MCP tool arguments.Still reproducible, and wider than Write/Edit on Linux.
Scope. On Windows 11 Pro 10.0.26220 (Git Bash, pwsh 7) the
\uXXXXdecode happens in theBash tool, the PowerShell tool and the Write tool alike, so it is not a Write/Edit bug. It sits
upstream of tool dispatch.
Evidence that it is upstream. A PreToolUse hook that inspects
tool_inputnever sees theescape: a quoted heredoc body containing
\u03b1reaches the hook with the escape alreadydecoded, and the file lands with the character. By contrast the Windows backslash collapse of
#88561 is visible to such a hook. The two rewrites are therefore at different layers, and this
one cannot be guarded or fixed at the tool level.
Decoder behaviour, measured through the Bash tool with
od -c:| input | result |
| --- | --- |
|
\u0041|A||
\u00e9| U+00E9 ||
\u4e2d| U+4E2D ||
\ud83d\ude00| U+1F600, surrogate pair correctly combined ||
\uZZZZ| unchanged ||
\n\t\"\b\r\0\x41| all unchanged |Correct surrogate-pair combining points at a real JSON string unescaper rather than a regex, applied
to a string that has already been JSON-decoded once. Only
\uis handled, which is why it staysunnoticed: every escape people check first looks fine.
A workaround exists on Windows, which I think answers the open question in the issue body
("neither produces the intended single-backslash literal"). On Linux with Write/Edit, no. But the
Bash tool on Windows can write a literal
\uXXXX, by doubling the backslash. This tool call:produces on disk, verified with
od -c:which is correct JSON for both fields. The doubling survives because the Bash-specific collapse of
#88561 also applies on this path; I am reporting that as an empirical recipe rather than a claim
about the relative order of the two passes, since the PowerShell tool preserves doubled backslashes
while still decoding
\u, and I cannot see the internals.So on Windows the two bugs partly cancel, and the Bash tool is the only one of the three that can
emit a literal
\uXXXXat all.