Skip to main content
ChatCLI implements a JSON recovery system that automatically fixes malformed arguments generated by LLMs during tool calls. This dramatically increases the success rate of tool calls, especially with smaller models or via XML parsing.

Why Malformed JSON?

LLMs frequently generate invalid JSON in their tool call arguments. This happens because language models work with tokens, not syntax validation:
ProblemExampleFrequency
Single quotes instead of double{'cmd': 'read'}Very common
Unquoted keys{cmd: "read", file: "main.go"}Common
Trailing commas{"cmd":"read","file":"main.go",}Common
Raw value without objectmain.go instead of {"file":"main.go"}Frequent
Mixed styles{cmd: 'read', "file": main.go}Occasional
CLI text instead of JSONread --file main.goFrequent
JS object literals{cmd: read, file: main.go}Occasional
Without JSON recovery, these errors cause parsing failures that break the agent loop. With smaller models, up to 30% of tool calls may have invalid JSON.

The 7 Recovery Strategies

The NormalizeToolArgs system applies up to 7 strategies in sequence, stopping at the first one that produces valid JSON:
Attempts direct parsing with json.Unmarshal. If the JSON is already valid, it returns immediately without modifications.
{"cmd":"read","args":{"file":"main.go"}}  =>  valid, returns directly
Converts single quotes to double quotes with proper escape handling. Preserves single quotes inside strings and escapes internal double quotes.
Input:  {'cmd':'read','file':'main.go'}
Output: {"cmd":"read","file":"main.go"}
The converter uses a stateful parser that tracks context (inside/outside string) to avoid incorrect substitutions.
Adds double quotes to keys that are not quoted. Uses regex to detect the {key: or , key: pattern.
Input:  {cmd: "read", file: "main.go"}
Output: {"cmd": "read", "file": "main.go"}
Applies the single quote fix first, then the unquoted keys fix. Resolves cases where both problems coexist.
Input:  {cmd: 'read', file: 'main.go'}
Output: {"cmd": "read", "file": "main.go"}
Removes commas before } or ] that make the JSON invalid.
Input:  {"cmd":"read","file":"main.go",}
Output: {"cmd":"read","file":"main.go"}
When the model sends only a raw value instead of a JSON object, the system wraps it in the correct field based on the tool name.
Tool: read_file    Input: main.go     => {"file":"main.go"}
Tool: run_command  Input: ls -la      => {"cmd":"ls -la"}
Tool: search_files Input: TODO        => {"term":"TODO"}
The mapping covers 30+ tools and aliases, including native functions (read_file, write_file) and coder subcommands (read, exec, search).
For object literals with no quotes at all, the system manually parses key: value pairs and rebuilds valid JSON.
Input:  {cmd: read, file: main.go, append: true}
Output: {"cmd":"read","file":"main.go","append":true}
Values are interpreted intelligently:
  • true/false -> booleans
  • Numbers -> JSON numbers
  • null/none -> null
  • Quoted strings -> strings (quotes removed)
  • Everything else -> string

Tool-to-Field Mapping

Plain string wrapping uses an extensive mapping of tool names to their primary input field:
ToolFieldExample
read_filefilemain.go -> {"file":"main.go"}
write_filefileoutput.txt -> {"file":"output.txt"}
list_directorydir./src -> {"dir":"./src"}
search_filestermTODO -> {"term":"TODO"}
run_commandcmdgo build -> {"cmd":"go build"}
run_testsdir./pkg -> {"dir":"./pkg"}
Wrapping is only applied when the value does not look like CLI arguments (no --flags) and does not start with { or [. This avoids conflicts with the existing CLI argument parser.

Unicode Quote Normalization

In addition to JSON recovery, ChatCLI normalizes curly (Unicode) quotes to straight (ASCII) quotes automatically. LLMs frequently generate typographic quotes that cause compilation errors:
CharacterUnicodeReplacement
' 'U+2018, U+2019' (straight single quote)
" "U+201C, U+201D" (straight double quote)
'U+2032 (prime)'
"U+2033 (double prime)"
<< >>U+00AB, U+00BB<< >>
Normalization is applied automatically to code files (60+ recognized extensions) and always to tool call arguments.

Configuration

The recovery system works automatically without configuration. All strategies are applied in order until one produces valid JSON.
VariableDescriptionDefault
(none)JSON recovery has no dedicated environment variablesAlways active
The recovery strategies are non-destructive: if none produces valid JSON, the original text is passed through so the CLI parser can attempt to interpret it as positional arguments.

Next Steps

Plugin @coder

Complete reference for the tools that use JSON recovery.

Native Tool Use

With native tool use, JSON comes validated by the API — less need for recovery.

Tool Result Management

How tool results are managed after execution.

Coder Mode

The complete engineering workflow with tool calls.