Skip to content

Release v6.8.3: malformed tool-call JSON repair - #52

Merged
code-crusher merged 3 commits into
mainfrom
release/6.8.3
Sep 4, 2026
Merged

Release v6.8.3: malformed tool-call JSON repair#52
code-crusher merged 3 commits into
mainfrom
release/6.8.3

Conversation

@code-crusher

Copy link
Copy Markdown
Member

Summary

Release 6.8.3 — resilient tool-call argument handling plus a model picker fix.

Fixed

  • Model picker now updates the header row's MODEL line. Selecting a model updated settings and the agent, but the header row's modelName was a stale snapshot — the MODEL/WORKSPACE line only refreshed on /new or /resume. switchModel now patches the header row in place.

Changed

  • Malformed tool-call JSON is now repaired instead of rejected. Models that emit almost-JSON — unquoted strings ("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_files numeric limits clamp instead of failing. max_results and context_lines values 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: 3 runs 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 updated
  • npx tsx --test test/files.test.ts — 2 tests (read_file defaults)
  • npx tsc --noEmit clean
  • test/models.test.ts failures verified pre-existing on origin/main (Axon model retirement, unrelated)

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.

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 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 and toolCall.arguments ? JSON.parse(...) : {} in handleToolCall. This PR passes the value straight into parseToolCallArguments, so a tool_call restored from a session file (or a provider payload) with arguments absent hits raw.trim() and throws a TypeError — crashing transcript/history replay on session resume, where the old code safely produced {}.

Fix: Accept undefined/null at 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 arguments field.

-  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 final JSON.parse still 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, via quoteAsJsonString) 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 : "\\\\"))}"`;

@code-crusher
code-crusher merged commit 01736d9 into main Sep 4, 2026
1 check passed
@code-crusher
code-crusher deleted the release/6.8.3 branch September 4, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant