[BUG] "no low surrogate in string" API error when plugins inject supplementary Unicode characters

Resolved 💬 3 comments Opened Mar 29, 2026 by DanDaltonSage Closed Apr 2, 2026

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

  1. Install several official plugins that contain emojis in their SKILL.md or documentation files (Vercel, Impeccable, Notion, Atlassian, Firecrawl, example-skills, etc.)
  2. Have a project with SessionStart hooks that inject additional context
  3. Start a new session: cd <project-dir> && claude
  4. Send any message (e.g. "hi")
  5. 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

  1. Enable 10+ official plugins (e.g. Vercel, Impeccable, Notion, Atlassian, Firecrawl, example-skills, document-skills, Superpowers, Figma, Telegram)
  2. Have a project with a CLAUDE.md (~5KB+) and a SessionStart hook that injects additional context
  3. Start a new session: cd <project-dir> && claude
  4. Send any message (e.g. "hi")
  5. 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_

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗