InputValidationError: long unicode-escaped tool arguments fail JSON parse (AskUserQuestion) — retry with shorter input succeeds
Note: This report separates what was directly observed in one session from a broader hypothesis based on recurring user experience. They may or may not be the same underlying defect.
---
Part 1 — Directly observed (reproduced this session)
AskUserQuestion was called with a long Korean question whose arguments serialized into many \uXXXX unicode escapes. The call failed with:
InputValidationError: AskUserQuestion was called with input that could not be parsed as JSON.
Common causes: unescaped backslashes ..., unescaped control characters, or truncated output.
A shorter, single retry of the same logical call succeeded. Same intent, shorter/cleaner serialization -> passed.
What is certain from this: the model sometimes emits malformed JSON in the tool-call arguments for long / heavily unicode-escaped inputs, causing a parse/validation failure. This part is reproduced and logged.
What is NOT certain: whether the failure is in argument serialization specifically, vs. the tool-call start token / framing. I cannot distinguish these from the surface error alone.
Part 2 — Hypothesis (recurring across sessions, NOT reproduced here)
Across multiple prior sessions (anecdotal, user-reported), a related-looking failure has been observed where the tool-call start token is emitted as plain text (fragments resembling ordinary words), breaking parsing — and where the error appears to repeat across consecutive turns as if the model imitates its own previous malformed output.
This pattern was NOT reproduced in the current session. It is included only as context in case it correlates with Part 1. Treat it as a hypothesis, not evidence.
Suspected amplifying conditions (correlational, not proven)
- Multiple tool calls in a single message (parallel)
- Long arguments, especially long non-ASCII / unicode-escaped text
- High effort level (
xhigh) producing longer outputs - Large persistent context (large
CLAUDE.md, many MCP tools loaded)
Workarounds that empirically reduce frequency
- One tool call per message (serial)
- After a failure, retry once with a shorter/cleaner call (worked in Part 1)
- Lower effort level (
xhigh->high) - Reduce tool surface area (remove unused MCP connectors)
Environment
- Claude Code: 2.1.181 (WinGet install)
- Model: Opus 4.8 (
claude-opus-4-8) - OS: Windows 11 (10.0.26200)
- Shell: Git Bash (POSIX) + PowerShell 5.1
Ask
Could maintainers confirm whether long unicode-escaped tool arguments are a known cause of InputValidationError JSON parse failures, and whether that is related to the broader start-token framing issue described in Part 2? Happy to capture a fuller transcript next time Part 2 reproduces.
10 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Additional evidence (second session, stronger pattern)
Reviewed another session's full transcript (a session heavy on Outlook MCP calls — reading ~13 emails + attachments). Out of 28 total tool failures, the dominant failure cluster (13 of them) was malformed MCP tool arguments, which is the same class as Part 1 above but with a much clearer signature.
Strong signals — model emitted malformed arguments
These are not "wrong target" errors; the arguments themselves were structurally broken:
| Count | Signature | Why it points to arg serialization |
|---|---|---|
| 5x |
get_email→400 Bad Request(very long message-id in URL) | Long ID arguments getting mangled in the request || 2x |
Missing required argument: attachment_id(get_attachment) | Required arg dropped from the tool call || 1x |
Missing required argument: account_id(get_email) | Required arg dropped from the tool call || 1x |
Invalid account_id format(get_attachment) | Arg value malformed || 2x |
Invalid non-printable ASCII character in URL, '\n' at position 209(get_email) | A literal newline was injected into an argument value — the clearest evidence |The
'\n' at position 209case is the smoking gun: the model inserted a stray newline into a long argument value, breaking the call. This is the same underlying behavior as the originalAskUserQuestionJSON parse failure (long argument → malformed serialization), just surfacing through a different tool and a different downstream validator.Weaker / possibly-unrelated
get_attachment→404 Not Found— could be a genuinely missing attachment rather than a malformed arg; included only for completeness, not as evidence.Updated read on this issue
\n) during tool-call argument serialization.Suggested repro angle for maintainers
Stress
get_email/get_attachment(or any MCP tool taking long ID-like args) with long argument values and observe whether stray\nor dropped required args appear in the outgoing call. Thenon-printable ASCII '\n' in URLvalidation error is a reliable detector.Correction + strong new evidence: the "self-imitation cascade" (Part 2) IS reproduced
In my earlier comment I said the Part 2 hypothesis (start-token / self-imitation cascade) was "not reproduced." That was wrong — I had only counted errors individually and missed the temporal cascade structure. Re-reading the transcript, there is a clear, logged cascade.
The cascade (single task: download one XLSX attachment, ~5 consecutive failures)
Smoking-gun behavioral evidence — the model's own words mid-cascade
\nfailure, the assistant stated: "a newline got mixed into the ID; I'll send it on a single line" — and then emitted\nagain on the very next call.\nkeeps getting inserted at position 209. The email_id is 200+ chars, so position 209 is inside the ID. This looks like the tool framework inserting line breaks into long argument values."So the model recognized the defect, announced a fix, and immediately repeated the same malformed output. That is the self-imitation cascade — now an observed fact, not a hypothesis.
Root cause (refined)
When multiple long base64 ID arguments (200+ chars) are sent in one tool call, the argument serialization intermittently:
\ninto the middle of a value (→non-printable ASCII '\n' in URL), orMissing required argument).This happens before the MCP server receives anything — the arguments are already corrupted at the model/harness serialization layer. The MCP server then correctly rejects the malformed input; it is not the cause.
Trigger conditions (now better characterized)
\nlands around position 209, inside the long ID)Why this matters for triage
The self-correction-then-repeat behavior suggests the corruption is not something the model can avoid by "trying harder" — it recurs deterministically under the trigger, which points to the serialization/encoding layer rather than model reasoning.
Part 2 reproduced at scale — stray token flood on SHORT-argument, non-MCP tools (self-imitation cascade)
The Part 2 hypothesis ("start-token emitted as plain text + self-imitation across turns") — previously marked not reproduced — has now reproduced, at large scale, in a different environment. Crucially, this time with short-argument built-in tools (Read/Bash/Edit/Playwright), no MCP and no long base64 IDs involved, which answers the open question of whether the defect is confined to long arguments: it is not.
Measured from the session transcript (JSONL, full scan)
| Metric | Value |
|---|---|
| Session length | 1,693 events / 342 assistant text blocks |
| Stray token (
court) total occurrences | 1,113 || Messages affected | 28 |
| Worst single message | 387 repetitions (4,162 chars) |
Behavior observed
courtleaks instead of (or interleaved with) the tool-call framing. The tool call is delayed or replaced by runs of the stray token./model) stopped it instantly, consistent with the corrupted pattern living in the accumulated self-context.Context before onset (possible triggers)
Updated read
\n, dropped required args) and Part 2 (start-token leakage + self-imitation) now both have direct transcript evidence.count/call-like fragments before,courtnow) — so it is not one magic word; the tool-call framing itself intermittently decodes as plain text, and the model then locks onto its own corrupted output.Environment: Claude Code on Windows 11 (10.0.26200), model Opus 4.8 (
claude-opus-4-8), long-running session with high context pressure. Happy to share the sanitized transcript excerpts if useful.Follow-up: exact substitution point captured (transcript forensics)
Located the first leak in the transcript. The failure mechanism is now precisely characterized:
courtwas emitted exactly where the tool-call opening frame should be, and the entire call body (<invoke>/<parameter>XML) then streamed as inert plain text inside the text block — the harness never saw a tool call at all.\ninjected at position 209, dropped args — where the call was established): here a single token substitution at the framing boundary inertizes the whole call.tool_useblock. Once the substitution lands, the model only re-emits text-mode pseudo-calls or bare stray tokens within that message.count/callanecdotally,courtconfirmed) are all shortc-English tokens — possibly tokenizer near-neighbors of the framing sequence. Not verifiable from outside; flagging in case it helps navigate logit/tokenizer data internally.What would confirm root cause (only visible internally): token-level logs/logits at the framing decision point in long-context sessions — distinguishing (a) sampling near-miss on the special token, (b) harness decode-path issue, (c) compaction artifact. From decoded transcripts these are indistinguishable.
---
Addendum — layer discrimination (evidence was already at hand; correcting my "indistinguishable" claim above):
| Evidence | If harness/API decode bug (b) | Observed |
|---|---|---|
|
/modelswitch (harness & decode path unchanged) | new model should break identically | instant, complete cure; all calls clean after || Onset pattern | deterministic (same input → same break) | hundreds of clean calls, then stochastic onset; successful calls interleaved even during the flood |
| Leaked artifact | fragments of the framing string expected | an intact ordinary vocabulary token (
court) || Amplification | a parser doesn't imitate context | self-imitation cascade (model-native behavior) |
→ Strongly favors (a) model-side sampling near-miss on the tool-call framing token, with the harness faithfully rendering the broken emission as text. Suggests triage should start at model/sampling under long-context pressure rather than CLI parse code. Not 100% conclusive without raw token logs, but the Bayesian lean is heavy.
Two actionable notes: a model-free mitigation, and a newer-model robustness observation
1. Harness-level mitigation (no model change required)
The failure signature is cheap to detect at the harness: an assistant text block containing tool-call XML (
<invoke name=...>/ parameter markup) that never materialized as atool_useblock. That is always a defect — it has no legitimate occurrence.A harness that detects this could break the cascade at ignition (n=1) instead of letting it amplify (we measured 0/34 in-message recovery once the pattern establishes):
Since amplification is driven by in-context self-imitation, removing the corrupted output from what the model sees next is the lever. Server-side constrained decoding at tool-call positions would prevent it outright, but the harness-side detector looks like the cheap, shippable version.
2. Newer model appears robust — including against an already-contaminated context
The strongest datapoint from our incident: when the user switched models mid-session (
/model), the successor model inherited the same context containing 1,113 stray-token occurrences across 28 messages — and never imitated it once. All subsequent tool calls were clean, immediately.Same harness, same contaminated context, different weights → clean behavior. This (a) further confirms the defect is model-side emission, and (b) suggests the failure mode may already be effectively addressed in newer-generation models — i.e., resolution may arrive via model succession even if older weights are never patched. If that matches internal expectations, the mitigation in (1) mainly matters for sessions pinned to older models.
Correction & full-session timeline: definitive tally, geometric escalation curve, trigger refinement
My earlier figures (1,113 total / worst 387) were a mid-flood snapshot — measured while the cascade was still growing. Definitive numbers from the complete transcript:
| Metric | Final value |
|---|---|
| Stray-token total (affected model, entire session) | 4,759 |
| Worst single message | 2,928 (next: 710, 387) |
| After model switch | 15 occurrences — **all verified as mentions (quoted inside bug-analysis prose/snippets); zero bare-line or call-boundary emissions** |
Timeline (the part that changes the trigger hypothesis)
Refined trigger read: it is not continuous-work duration. A 2.5-hour heavy-tooling stretch on the same session was perfectly clean; the failure ignited 18 minutes after resuming a 4-day-dormant, heavily-accumulated/compacted context, then grew geometrically (×1 → ×2,928 within 14 minutes). This points at context saturation/compaction state at resume as the precondition — possibly worth reproducing internally by resuming long-dormant, near-limit sessions rather than by running long fresh ones.
The successor-model observation from my previous comment also survives stricter scrutiny: post-switch occurrences of the stray token were individually classified — every one is a quotation inside forensic discussion, none at emission positions.
Corroborating from a different environment — same failure, much higher frequency.
Environment (differs from OP → looks cross-platform, not Windows-specific):
claude-opus-4-8[1m])xhigh; large global CLAUDE.md; many MCP servers loadedFrequency: hitting this dozens of times a day, not occasional. Consistent trigger:
AskUserQuestionwhose Korean question/option text serializes into long\uXXXX-escaped JSON.Verbatim error:
The payload was 2245 bytes for a one-line Korean question — each Hangul char becomes a 6-char
\uXXXXescape, so short prompts balloon into escape-dense JSON where one malformed escape / stray backslash / control char fails the whole parse. A single retry with the same intent parses and succeeds, matching Part 1.Amplifiers I can confirm on my side:
xhigheffort, large persistent context (big global CLAUDE.md + many MCP tools), and option/description text containing(...)or file paths with\. Trimming the payload and avoiding backslash paths noticeably lowers the rate.Adding this mainly as evidence it's not Windows-specific and is frequent for non-ASCII (Korean) locales, which points at argument serialization (Part 1) rather than the Part 2 start-token hypothesis.
We hit the same failure and can answer the Part 1 uncertainty (serialization vs. framing): it is the argument serialization — a single token-level glitch inside a
\uXXXXescape sequence.Definitive evidence: the exact malformed bytes
Our harness spawns the CLI programmatically (Agent SDK,
stream-json). A tool call with long Korean arguments failed with the same error:The CLI preserves the full raw unparsed input in the project transcript (
~/.claude/projects/<project>/<session>.jsonl, in the assistant message's tool_use block asinput.__unparsedToolInput.raw, capped at 2048 chars). Recovering it and running it through a JSON parser pinpoints the defect at char 1712:The model chose to spell the entire Korean payload as ASCII
\uXXXXescapes (360 of them in this call) and, mid-word, emitted\followed by the raw character 로 (U+B85C) instead of the digitsub85c. It was spelling 으로 as으로and glitched halfway through the second escape.\로is an invalid JSON escape, so the entire ~2.6 KB input is unparsable.Everything else about the input is well-formed — escape-spelled CJK arguments normally parse fine (we have plenty of successful calls with the same spelling in the same sessions). The failure is not the escape spelling per se, nor length, nor framing: it's a sampling glitch that mixes the escape spelling with the raw-character spelling inside one escape sequence.
Corroborating details
__unparsedToolInput: 13 unparsable tool inputs total, of which only this one is the mixed-escape glitch (the rest are ordinary malformations — literal control characters in strings, trailing commas, extra data). So this failure mode is real but rarer than generic JSON glitches.Environment
@anthropic-ai/claude-agent-sdk0.3.201 (stream-json)claude-opus-4-8mcp__verbs__invoke)Repro tip for anyone else hitting this: grep your
~/.claude/projects/**/*.jsonlfor__unparsedToolInputand run therawfield through any strict JSON parser — the parse error position shows exactly what the model emitted.Corroborating evidence: same failure family reproduces in Traditional Chinese (zh-TW), not Korean-specific
Reviewed my own transcript history (
~/.claude/projects/**/*.jsonl) across the last 2 weeks (2026-07-15 to 2026-07-28) and found 15 occurrences ofInputValidationError: JSON parse failed, all inAskUserQuestionor nearby tools, spanning CLI versions 2.1.206 through 2.1.218, on macOS (darwin-arm64). All content is Traditional Chinese. This confirms the bug is not Korean-specific — it reproduces with a different CJK language, across many consecutive CLI releases (not one bad build).Impact observed: zero. Every occurrence self-recovered — the CLI/agent retried automatically and moved on. No session got stuck. Sharing purely as corroborating forensic data for prioritization, since Part 1 and Part 2 of the original report were each based on a single session.
Part 1 confirmed — mid-\uXXXX escape glitch (matches #79339 exactly)
One example, run through a strict JSON parser to pinpoint the exact byte:
Same mechanism as #79339: the model is mid-way through spelling a Chinese word as
\uXXXX, then instead of completing the 4 hex digits it drops straight into literal ASCII text (\uimmediately followed by a space + the literal lettersUI, not the intended hex digits) — one incomplete escape breaks the entire payload.Tool affected:
AskUserQuestion. 7 of my 15 occurrences match this exact sub-pattern, byte sizes ranging 347–2354 bytes — i.e. not exclusively a "very long input" thing, a 347-byte call failed too.Part 2 confirmed — tool-framing token leaks into JSON value (matches the "self-imitation cascade" comments below)
Right where a JSON array value is expected after
"questions":, the model instead emits a literal newline followed by<parameter name="question">— tool-invocation framing syntax leaking into the argument position, same family as the<invoke name="Bash">leak documented further down this thread. 2 of my 15 occurrences match this sub-pattern.---
Happy to share more raw
__unparsedToolInput.rawtranscript excerpts (with project-internal content redacted) if it helps narrow down the exact trigger.