JSON.stringify produces lone surrogates in API request body (400 invalid high surrogate)
Bug
Claude Code occasionally sends API requests containing lone Unicode surrogates (U+D800–U+DFFF), causing the Anthropic API to reject them with:
API Error: 400 {"type":"error","error":{"type":"invalid_request_error",
"message":"The request body is not valid JSON: invalid high surrogate in string: line 1 column 411126 (char 411125)"}}
Root Cause
Node.js strings are internally UTF-16. When in-memory strings contain lone surrogates (from file reads, terminal output, or web content), JSON.stringify() serializes them as \uD8xx without a following low surrogate — which is invalid JSON per RFC 8259.
The stored JSONL files are clean (verified: no surrogates in the JSONL, subagent files, or tool-result cache). The lone surrogate is introduced transiently during request body construction in Node.js memory.
Common triggers on Windows
- Emoji in code/comments read from files
- Box-drawing characters from terminal/build output
- Scraped web content with malformed UTF-16
- Any
Bashtool result containing non-BMP characters
Suggested Fix
Sanitize strings before JSON.stringify() when constructing the API request body. Node.js 20+ has a one-liner:
const safe = str.toWellFormed() // replaces lone surrogates with U+FFFD
For older Node.js:
str.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, '\uFFFD')
.replace(/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, '\uFFFD')
Impact
- Session becomes stuck (the failed response is recorded, retrying may work but isn't guaranteed)
- Loss of in-progress work context if user has to start a new session
- More likely on Windows due to cp1252/UTF-16 encoding mismatches
Environment
- Windows 11 Pro
- Claude Code VS Code extension
- Node.js (whatever version ships with Claude Code)
- Python 3.14 confirmed the JSONL files themselves are valid UTF-8
Reproduction
Happens consistently when copying structured output (bullet points, chat bubbles, etc.) from another chat window into the Claude code VS extension chat field and sending it.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗