Skip to content

Conversation

@crottolo
Copy link

@crottolo crottolo commented Jan 9, 2026

Fixes #7512

Problem

LLMs sometimes send tool parameters as strings instead of proper typed values:

{ "todos": "[{...}]" }    // string instead of array
{ "timeout": "180000" }   // string instead of number  
{ "enabled": "true" }     // string instead of boolean

This causes Zod validation to fail with expected X, received string.

Solution

Added automatic coercion in Tool.define():

  1. First attempt normal validation with safeParse
  2. If fails with type error, coerce string values:
    • [ or { prefix → JSON.parse() for arrays/objects
    • Numeric string → Number() for numbers
    • "true"/"false" → boolean conversion
  3. Retry validation with coerced values
  4. If still fails, throw original error (backward compatible)

Verification

Tested all affected tools:

✅ Bash (timeout: number)
✅ Todowrite (todos: array)
✅ Webfetch (timeout: number)
✅ Websearch (numResults: number)
✅ LSP tools (line, character: number)
✅ Read (offset, limit: number)
✅ Normal typed values still work unchanged

Scope

  • 10+ tools affected: Bash, Todowrite, Question, Webfetch, Websearch, LSP, Edit, Multiedit, etc.
  • All providers: Anthropic, OpenAI, Google, etc.
  • Backward compatible: Yes

When LLMs return tool call arguments, they sometimes serialize arrays
and objects as JSON strings instead of proper JSON values. This causes
Zod validation to fail with 'expected array, received string'.

This fix adds automatic coercion: if validation fails because a string
was received where an array/object was expected, we attempt to parse
the string as JSON and retry validation.

Affected tools: Todowrite, Question, and any tool with array/object params.
Applies to all providers (Anthropic, OpenAI, Google, etc.).
@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

The following comment was made by an LLM, it may be inaccurate:

Based on my search, the only related PR that came up alongside PR #7513 is:

PR #4715: "fix(write): support array content from Venice.ai models"

This PR appears related as it also deals with handling array content from LLM models, which touches on similar issues of how arrays are passed and validated in tool parameters.

However, this is an older PR (from the numbering), and the current PR #7513 appears to be a more comprehensive solution addressing stringified JSON in general (not just Venice.ai), with proper coercion logic using Zod's safeParse and JSON.parse().

The searches did not reveal any open PRs directly duplicating the current PR's scope. The current PR (#7513) is the most recent one addressing this stringified JSON coercion issue.

LLMs also send numbers as strings (e.g., timeout: "180000") and
booleans as strings (e.g., enabled: "true"). Extended coercion to
handle these cases as well.

Now handles:
- array/object: JSON.parse()
- number: Number() for numeric strings
- boolean: "true"/"false" conversion
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.

Tools with array/object parameters fail when LLM sends stringified JSON

1 participant