[BUG] Attaching/pasting an image fails with 400 ... str is not valid UTF-8: surrogates not allowed
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?
Whenever I attach or paste an image into the Claude Code chat, the request fails with:
API Error: 400 The request body is not valid JSON: str is not valid UTF-8: surrogates not allowed: line 1 column 1 (char 0)
This happens with any image, including fresh screenshots saved to the Desktop — so it is not a corrupted file. The error is reproducible across multiple different projects and has occurred consistently over several days. It looks like the client-side image-attachment encoding path is producing invalid UTF-8 (lone/unpaired surrogates) in the JSON request body.
Claude Code running inside the Claude Desktop app for Mac
App version: 1.14271.0
macOS (Apple Silicon)
What Should Happen?
The image is encoded correctly (base64 image block) and the request succeeds.
Error Messages/Logs
API Error: 400 The request body is not valid JSON: str is not valid UTF-8: surrogates not allowed: line 1 column 1 (char 0)
Steps to Reproduce
Open Claude Code inside the Mac desktop app.
Take a screenshot and either paste it (Cmd+V) or attach the saved PNG file.
Send the message.
The request fails with the 400 ... surrogates not allowed error.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
1.14271.0 (c8f4d8) 2026-06-18T05:47:57.000Z
Platform
Other
Operating System
macOS
Terminal/Shell
Other
Additional Information
_No response_
Showing cached comments. Read the full discussion on GitHub ↗
7 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Reproducible on Linux/X11 too — so this is not macOS/desktop-specific; it points to the client-side image-attachment encoding path in core.
Environment
2.1.197v24.6.0DISPLAY=:1)Evidence isolating it to the client (not the source image or the clipboard layer)
The clipboard held a perfectly valid PNG, confirmed with
xclipbefore pasting:So the image bytes and the X11 clipboard are fine. The lone/unpaired surrogates are introduced during Claude Code's image-attachment serialization, before the request leaves the client — matching the
line 1 column 1 (char 0)position.Workaround that avoids the bug: referencing the file via
@/path/to/image.png(or dragging the file in) instead of pasting with the clipboard. That code path does not hit the error.Also hitting this on macOS: 26.5.2.
Claude Code: 2.1.199.
Happens on every clipboard paste — error fires but image arrives on retry.
Workaround that works for me: reading the file directly by path instead of pasting from clipboard. Screenshots save to ~/Pictures, then reference via file path in chat. Avoids the serialization bug entirely.
(Work around which takes longer)
Broke 2026-07-02, was working on the 1st of July and prior.
Confirming on macOS, VSCode extension (native/embedded, not a shelled-out terminal
claude), v2.1.201 — same400 ... surrogates not allowed: line 1 column 1 (char 0). In my case it fired when a pasted screenshot was submitted as part of a queued message (image pasted while Claude was still mid-task), after which every subsequent turn returned the same 400.Two datapoints that may help localize it:
.jsonltranscript and it's clean UTF-8 with no lone surrogates anywhere — the attached image's base64 is pure ASCII. So the bad surrogate is introduced when the request body is built/encoded, not stored on disk.--resumestep; the tab just picked up where it left off and worked. So this behaves like a transient encode-time bug rather than corrupted/persisted session state, which distinguishes it from the reports where the session is permanently bricked.Follow-up: I've now hit this with no image attached, mid-stream, on the same setup — so the image path may not be the whole story. I'm behind Netskope Secure Web Gateway (TLS inspection), and on the failing turns there's a long pause followed by the whole response arriving in a single burst rather than streaming incrementally — i.e. the gateway is buffering the full SSE response body. With the gateway off / on a non-Netskope VM, streaming works and the error disappears.
Working theory: the gateway buffers and re-chunks the
text/event-streamand splits a multi-byte UTF-8 sequence at a chunk boundary → lone surrogate → replayed on the next request →400 surrogates not allowed. Full write-up with a differential test and a suggested client-side fix (decode UTF-8 across chunk boundaries / sanitize lone surrogates) in #64777 — quite possibly the same root cause as the image-attach path here.Confirming this on Windows 11, VS Code extension, CLI + extension both on v2.1.205 (latest as of 2026-07-09).
Reproduction:
API Error: 400 The request body is not valid JSON: str is not valid UTF-8: surrogates not allowed: line 1 column 1 (char 0)Ruled out while investigating:
This looks distinct from the emoji/truncation-boundary surrogate fixes shipped in v2.1.172/v2.1.187 — no emoji or special characters involved, just repeated image pastes.
Likely cause found: the affected laptop has Netskope's endpoint agent installed and running (EPDLP endpoint-DLP module + STAgent SSL-interception/traffic-steering module). Netskope's DLP inspects clipboard content, and its SSL-interception agent decrypts/inspects/re-encrypts HTTPS traffic (including this app's API calls) for content scanning before forwarding it. That would explain the 400 coming from the API itself rather than a client-side crash — the request body is likely being corrupted in-transit during Netskope's inspection/re-encoding of the payload, which is more likely to surface on larger binary payloads like base64-encoded images than plain text. My other, unaffected laptop does not have this agent installed. If others hitting this are on a corporate network with a TLS-inspecting/DLP proxy (Netskope, Zscaler, etc.), that's worth checking — the fix on my end will likely be an SSL-inspection/DLP bypass exception for Anthropic's API domains rather than anything in Claude Code itself.
(Secondary, likely unrelated: the affected laptop has no admin rights so runs Claude Code's "native" self-contained binary rather than a global npm install — mentioning in case it's relevant, though the Netskope finding above seems the stronger lead.)
Also reproduces in the plain
claudeCLI, not just the VS Code extension — same error, same "every subsequent request fails" behavior. Since the CLI and the VS Code extension use different mechanisms for attaching/pasting images, this rules out anything specific to VS Code's own clipboard-paste handling and points at something shared by both entry points: either the core CLI binary's request/encoding logic, or the network path (which for me means Netskope, intercepting everyclaude.exeflow to api.anthropic.com regardless of which entry point launched it).