Release v6.8.3: malformed tool-call JSON repair - #52
Conversation
Models that emit almost-JSON (unquoted strings, unquoted keys, single quotes, trailing commas, Python literals, comments, truncated output) no longer burn a round trip on a corrective error they tend to repeat. A best-effort repair pass recovers the intended arguments, the tool executes with them, and a note on the tool result tells the model what actually ran. search_files max_results/context_lines now clamp to the nearest bound instead of failing the whole search.
…quotes
Models sometimes emit internal markup tags (<longcat_arg_key>,
<longcat_arg_value>) where JSON punctuation belongs, or drop a key's
closing quote ("offset: 600). The repair pass now strips XML-style
tags before scanning and splits broken key-position strings at the
first colon (else whitespace) so the head becomes the key and the
tail is re-scanned as the value. Tags inside valid quoted strings are
legitimate content and stay untouched. read_file already defaults
missing offset/limit at the executor, so repaired calls run with
sane bounds.
There was a problem hiding this comment.
🧪 PR Review is completed: Well-built JSON repair pipeline with thorough test coverage; the agent-side repair-note plumbing and graceful search-limit clamping are solid. Two hardening gaps in the new parser: removed falsy-guards on tool-call arguments make it crash-prone on session-restored data, and invalid escape sequences (un-escaped Windows paths) defeat the repair. Reviewed src/core/agent.ts (repair-note plumbing, transcript repair): no issues found. Reviewed src/ui/App.tsx: no issues found. Reviewed src/tools/executors/searchFiles/types.ts: no issues found. Reviewed src/api/aiSdkClient.ts: no issues found. Reviewed test/json-repair.test.ts: no issues found. Reviewed test/search-files.test.ts: no issues found. Reviewed package.json: no issues found.
Skipped files
CHANGELOG.md: Skipped file pattern
⬇️ Low Priority Suggestions (2)
src/utils/jsonRepair.ts (2 suggestions)
Location:
src/utils/jsonRepair.ts(Lines 23-26)🟡 Null Safety
Issue: The old call sites guarded against missing arguments — agent.ts used
JSON.parse(call.function.arguments || "{}")for transcript building andtoolCall.arguments ? JSON.parse(...) : {}inhandleToolCall. This PR passes the value straight intoparseToolCallArguments, so a tool_call restored from a session file (or a provider payload) withargumentsabsent hitsraw.trim()and throws a TypeError — crashing transcript/history replay on session resume, where the old code safely produced{}.Fix: Accept
undefined/nullat the module boundary and treat them as empty input. This single-point hardening restores the old defensive behavior for every caller (agent.ts:305, agent.ts:1216) without touching call sites.Impact: Prevents a crash during session resume when stored tool_calls lack an
argumentsfield.- export function parseToolCallArguments( - raw: string, - ): ParsedToolCallArguments | null { - const trimmed = raw.trim(); + export function parseToolCallArguments( + raw: string | undefined | null, + ): ParsedToolCallArguments | null { + const trimmed = (raw ?? "").trim();Location:
src/utils/jsonRepair.ts(Lines 201-201)🟡 Robustness
Issue: When an intact quoted string is copied verbatim (line 201), invalid escape sequences survive the repair. E.g.
{"path": "C:\Users\x", "file_pattern": *.ts}fails strict parse, but the repair re-emits"C:\Users\x"unchanged, so the finalJSON.parsestill fails and the entire call is rejected as unrecoverable. Un-escaped Windows paths are one of the most common LLM JSON malformations for file-oriented tools, and the truncated-string path (line 198, viaquoteAsJsonString) already handles them — only this intact-string path does not.Fix: When re-emitting an intact string, escape lone backslashes that are not part of a valid JSON escape sequence (leaving valid
\,,\",\u…sequences untouched).Impact: Repairs a common malformation class instead of dead-ending the tool call with a re-issue request.
- out += source.slice(index, end + 1); + out += `"${content.replace(/\\\\|\\(?!["\\\\/bfnrtu])/g, (m) => (m.length === 2 ? m : "\\\\"))}"`;
Summary
Release 6.8.3 — resilient tool-call argument handling plus a model picker fix.
Fixed
settingsand the agent, but the header row'smodelNamewas a stale snapshot — the MODEL/WORKSPACE line only refreshed on/newor/resume.switchModelnow patches the header row in place.Changed
"file_pattern": *.tsx), unquoted keys, single quotes, trailing commas, Python literals (True/None), comments, XML-style tags interleaved where punctuation belongs ("offset</longcat_arg_key>), keys with dropped closing quotes ("offset: 600), or output truncated mid-call — no longer burn a round trip on a corrective error (weaker models repeated the same mistake on retry). A best-effort repair pass (src/utils/jsonRepair.ts) recovers the intended arguments, the tool executes with them, and a note on the tool result tells the model what actually ran; only truly unrecoverable arguments still return the corrective error. Session replay and the AI SDK history path use the same repair so the model sees its own repaired calls.search_filesnumeric limits clamp instead of failing.max_resultsandcontext_linesvalues that are fractional, out of range, or numeric strings now clamp to the nearest bound (or fall back to the default when non-numeric) instead of failing the whole search — e.g.context_lines: 3runs with 2.Test plan
npm run test:json-repair— 13 tests: strict pass-through, unquoted values/keys, single quotes, Python literals, comments, truncation, XML tag stripping, dropped key quotes, boundary cases (valid JSON untouched, tags-in-strings preserved, unrecoverable → null)npm run test:search— 11 tests, clamping assertions updatednpx tsx --test test/files.test.ts— 2 tests (read_file defaults)npx tsc --noEmitcleantest/models.test.tsfailures verified pre-existing onorigin/main(Axon model retirement, unrelated)