Symptom
Any tool call carrying non-ASCII text (Korean, em dash, curly quotes, arrows) is frequently rejected before execution with:
Tool call "bash" spelled non-ASCII text as \uXXXX escapes instead of literal UTF-8.
Escaped text cannot be verified - a single wrong hex digit silently becomes a different
character - so the call was not executed. Re-issue it writing every non-ASCII character literally.
The guard itself (findUnnecessaryUnicodeEscape in packages/ai/src/utils/json-parse.ts -> escapedNonAsciiArguments -> rejection in packages/agent/src/agent-loop.ts) is correct: hand-spelled hex parses cleanly but one mistyped nibble decodes to a different, equally valid character, and nothing can repair that after parsing.
The problem is the recovery path, not the detection.
Measured impact
Local session logs, most recent 60 sessions, 7910 tool calls:
|
tool calls |
non-ASCII present |
rejected |
| all |
7910 |
175 |
116 (66%) |
| Hangul-carrying |
- |
85 |
66 (78%) |
Per model: claude-opus-5 53/54 Hangul calls rejected, claude-sonnet-5 13/13, gpt-5.6-sol/gpt-5.6-terra 0/18. Only the anthropic-messages path is affected.
Not a transport defect
Direct probes against the same proxy and model could not reproduce it: 25+ curl runs varying payload length, thinking on/off, fine-grained-tool-streaming beta on/off, 30 extra tools, cloaked mcp__... tool names, 40-turn filler context, and a primed history containing the rejection error itself - all returned literal UTF-8, zero escapes. Six real gjc -p runs with wire capture (45-76 tools, ~200KB requests, one 929-Hangul-character write call) also produced zero escapes.
It is an intermittent sampling accident that shows up in long sessions. In session logs the flag clusters in runs with 700-1000 assistant messages and fires in consecutive bursts (F@144 F@145 F@146 F@147).
Why the current handling makes it worse
- A wire-format defect is promoted to a tool error, spending the entire turn.
- The rejection text re-injects the literal
\uXXXX syntax into the context the model samples from next.
- There is no bounded resample, and no config switch (0 related keys in
config.schema.json).
The anthropic.ts thinking-replay repair already establishes the right shape for this class of fault: when the defect is visible before the turn is usable, drop it and re-request within a bounded budget instead of burning the turn.
Fix
Bounded turn resample in the non-managed session path, alongside the existing invalid_prompt and reasoning-content repairs: drop the defective assistant turn from history and re-request, up to twice per turn. The terminal per-call rejection stays as the answer once the budget is spent, and MAX_CONSECUTIVE_MALFORMED_TURNS remains reachable as the loop backstop.
Symptom
Any tool call carrying non-ASCII text (Korean, em dash, curly quotes, arrows) is frequently rejected before execution with:
The guard itself (
findUnnecessaryUnicodeEscapeinpackages/ai/src/utils/json-parse.ts->escapedNonAsciiArguments-> rejection inpackages/agent/src/agent-loop.ts) is correct: hand-spelled hex parses cleanly but one mistyped nibble decodes to a different, equally valid character, and nothing can repair that after parsing.The problem is the recovery path, not the detection.
Measured impact
Local session logs, most recent 60 sessions, 7910 tool calls:
Per model:
claude-opus-553/54 Hangul calls rejected,claude-sonnet-513/13,gpt-5.6-sol/gpt-5.6-terra0/18. Only theanthropic-messagespath is affected.Not a transport defect
Direct probes against the same proxy and model could not reproduce it: 25+ curl runs varying payload length, thinking on/off,
fine-grained-tool-streamingbeta on/off, 30 extra tools, cloakedmcp__...tool names, 40-turn filler context, and a primed history containing the rejection error itself - all returned literal UTF-8, zero escapes. Six realgjc -pruns with wire capture (45-76 tools, ~200KB requests, one 929-Hangul-characterwritecall) also produced zero escapes.It is an intermittent sampling accident that shows up in long sessions. In session logs the flag clusters in runs with 700-1000 assistant messages and fires in consecutive bursts (
F@144 F@145 F@146 F@147).Why the current handling makes it worse
\uXXXXsyntax into the context the model samples from next.config.schema.json).The
anthropic.tsthinking-replay repair already establishes the right shape for this class of fault: when the defect is visible before the turn is usable, drop it and re-request within a bounded budget instead of burning the turn.Fix
Bounded turn resample in the non-managed session path, alongside the existing
invalid_promptand reasoning-content repairs: drop the defective assistant turn from history and re-request, up to twice per turn. The terminal per-call rejection stays as the answer once the budget is spent, andMAX_CONSECUTIVE_MALFORMED_TURNSremains reachable as the loop backstop.