MCP channel notification ingestion truncates mid-surrogate, persisting a lone surrogate into the transcript (400 no low surrogate in string)
Summary
When a long notification arrives over an MCP channel (--channels), Claude Code truncates the ingested message to ~1313 characters while building the transcript records. If that truncation boundary falls between the two halves of a UTF-16 surrogate pair (e.g. an emoji like 🔑 = U+D83D U+DD11), the low surrogate is dropped and an orphaned high surrogate is persisted into the session .jsonl. Every subsequent request to the Anthropic API then re-serializes that lone \ud83d, and the API's strict JSON parser rejects it:
400 The request body is not valid JSON: no low surrogate in string: line 1 column NNNNNN
The session is bricked until the transcript is manually repaired — the bad byte poisons every retry.
Impact
Permanent session death from a single inbound channel message. Not recoverable by retry; requires hand-editing the .jsonl to strip the lone surrogate.
Root cause (observed)
- The corrupted strings appeared twice in the transcript: a queue-operation
.contentrecord and the user.message.content, both exactly 1313 chars, both with a lone high surrogateU+D83Dat index 1301, with text continuing after it. - The originating message was ~600 chars longer than the stored 1313 — i.e. it was truncated at ingest, and the cut landed mid-surrogate.
- The truncation is in Claude Code's MCP-channel-notification → transcript ingestion, not in the channel sender or the MCP plugin (verified: the plugin transmits the full payload; only a separate 200-char preview field is sliced elsewhere).
Suggested fixes
- Make the ingest truncation surrogate-aware — never cut between a high/low surrogate; drop a trailing lone surrogate after truncating.
- Sanitize lone surrogates before persisting any content to the transcript (belt-and-suspenders).
Environment
Claude Code CLI on Windows 11, MCP channels plugin.
---
Related: #36503 (also in the --channels / MCP-ingest area, though a distinct symptom).
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗