[BUG] "no low surrogate in string" API error when plugins inject supplementary Unicode characters
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?
Description
Claude Code v2.1.87 fails with a JSON serialisation error when the assembled system prompt payload contains supplementary Unicode characters (U+10000+, i.e. emojis like 📖, 🔥, 🎯, 🤝, etc.). The error occurs during the API call, not during file reading — the JSON body sent to the Anthropic API contains malformed surrogate pairs.
Error
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"The request body is not valid JSON: no low surrogate in string: line 1 column 91773 (char 91772)"},"request_id":"req_011CZXGb3Qin4Rh9s4CC9ZN3"}
Environment
- Claude Code version: 2.1.87
- Node.js version: v23.11.0
- Platform: macOS Darwin 25.3.0 (arm64)
- Shell: zsh
- Locale:
LANG=en_GB.UTF-8
Node.js itself handles supplementary Unicode correctly — JSON.stringify / JSON.parse roundtrips emojis without issue. The bug is in how Claude Code assembles or transmits the request body.
Reproduction
- Install several official plugins that contain emojis in their SKILL.md or documentation files (Vercel, Impeccable, Notion, Atlassian, Firecrawl, example-skills, etc.)
- Have a project with SessionStart hooks that inject additional context
- Start a new session:
cd <project-dir> && claude - Send any message (e.g. "hi")
- First API call fails with the "no low surrogate in string" error
The error is payload-size-dependent — it only manifests when enough plugins are loaded to push the system prompt past a certain size. With fewer plugins or smaller project context, the same emojis don't trigger the error.
Key observations
- The column position in the error shifts as content is added/removed, confirming it's a specific character in the serialised payload
- All official plugin publishers use emojis freely in their SKILL.md files (category labels like
🎯 Product Strategy,📊 Business Analysis, doc links like📖 docs:, etc.) - The emojis are valid UTF-8 on disk —
node -e "console.log(JSON.stringify('🔥'))"works correctly - The bug appears to be in the serialisation step that builds the HTTP request body, not in file reading
Workaround
Strip all supplementary Unicode characters (U+10000+) from plugin and project files:
find ~/.claude ~/.cache/plugins ~/my-project -type f \
\( -name "*.md" -o -name "*.js" -o -name "*.mjs" -o -name "*.ts" \
-o -name "*.json" -o -name "*.txt" -o -name "*.cjs" -o -name "*.sh" \) \
-exec python3 -c "
import sys; f=sys.argv[1]; d=open(f,'rb').read(); t=d.decode('utf-8')
c=''.join(x for x in t if ord(x)<=0xFFFF)
if c!=t: open(f,'w').write(c); print(f'Cleaned {f}')
" {} \; 2>/dev/null
This workaround is fragile — plugin auto-updates restore the emojis and the error returns.
What Should Happen?
Expected behaviour
Claude Code should correctly serialise all valid Unicode characters (including supplementary plane characters U+10000+) when building the API request body, regardless of payload size.
Files affected (examples from official plugins)
~/.cache/plugins/github.com-vercel-vercel-plugin/vercel.md— 21📖characters~/.cache/plugins/.../impeccable/*/scripts/build.js—🎨,📦,🔨,📖,📋~/.cache/plugins/.../claude-plugins-official/Notion/*/README.md—🚀,📦,🔑,🙌~/.cache/plugins/.../claude-plugins-official/firecrawl/*/skills/firecrawl-cli/SKILL.md—🔥~/.claude/skills/*/SKILL.md(Superpowers PM skills) — category emojis like🎯,📊,🤝,🗣,💾,🔍,🎨,💡,🌱~/.cache/plugins/.../atlassian/.../report-templates.md—🟢,🟡,🔴
Error Messages/Logs
Steps to Reproduce
- Enable 10+ official plugins (e.g. Vercel, Impeccable, Notion, Atlassian, Firecrawl, example-skills, document-skills, Superpowers, Figma, Telegram)
- Have a project with a CLAUDE.md (~5KB+) and a SessionStart hook that injects additional context
- Start a new session:
cd <project-dir> && claude - Send any message (e.g. "hi")
- First API call fails with "no low surrogate in string" error
The error is payload-size-dependent — it only triggers when the total system prompt exceeds ~80-90K characters AND contains at least one supplementary Unicode character
(U+10000+, e.g. emojis like 📖, 🔥, 🎯). Below that payload size, the same emojis work fine.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
latest
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗