fix(tools): report bad tool arguments as a sentence, not a JSON dump - #2559
Merged
Merged
Conversation
A `ZodError`'s `message` is its pretty-printed `issues` array, and
`ToolRegistry.execute` let one escape from `tool.parse(rawArgs)` straight
into the agent loop's `Error: ${msg}` tool result. A model that got one
field wrong therefore printed forty lines of `{"expected": "string",
"code": "invalid_type", "path": ["todos", 0, "content"]}` into the chat.
Both audiences are worse off for it: the user reads a wall of JSON with
the actual problem buried in it, and the model reads a payload to parse
instead of an instruction it can act on, so its retry is a guess.
Restate argument-validation failures at the registry, which is the one
place that knows the arguments — rather than the tool's own execution —
were at fault:
update_todos: the arguments did not match the tool's schema.
todos[0].content — expected string, received undefined.
Correct them and call the tool again.
Paths render as `todos[0].content` so the field is findable in the call
the model just made; zod's redundant "Invalid input:" prefix is dropped;
the list caps at five fields and then counts the rest. Errors thrown from
inside a tool are untouched — only a `ZodError` is restated.
Reported against a qwen3-235b plan whose first todo carried no `content`.
Fixes #2442
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AZp6uiHQb3WbsHWyQqG76
jonathanKingston
marked this pull request as ready for review
September 9, 2026 01:27
Contributor
🖥️ PR preview
|
jonathanKingston
deleted the
claude/validate-outstanding-issues-bdhpr7
branch
September 9, 2026 01:52
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
A
ZodError'smessageis its pretty-printedissuesarray.ToolRegistry.executelet one escape fromtool.parse(rawArgs)(tool-registry.ts:139), and the agent loop splices whatever it catches intoError: ${msg}as the tool result (run-agent-loop.ts:949). So a model that gets one field wrong prints this into the chat:Both audiences are worse off. The user reads a wall of JSON with the real problem buried in it. The model reads a payload to parse rather than an instruction it can act on, so its retry is a guess.
The change
Restate argument-validation failures at the registry — the one place that knows the arguments, rather than the tool's own execution, were at fault:
todos[0].content, matching the shape of the call the model just made, so the field is findable without translating a JSON pointerInvalid input:prefix is dropped, since the surrounding sentence already says itand N more— past a handful the model needs to re-read the schema, not a longer listdescribeToolArgErrorreturnsnullfor anything that isn't aZodErrorThis helps every tool, not just
update_todos— the same escape existed for all of them.Scope
The issue also asks for tolerance of near-miss todo shapes. I have not done that, deliberately: the screenshot captures the error but not the arguments, so I would be guessing at what qwen actually sent (
titleforcontent? a bare string?). Inventing aliases on a guess is how you get a schema nobody can reason about.A readable message plausibly closes most of the gap on its own, since it tells the model exactly which field to fix. If it recurs, the captured payload is what's needed to design tolerance properly — worth reopening with one attached.
Testing
tool-arg-error.test.ts— the reported missing-contentplan, the dropped prefix, the five-field cap, a wholly wrong argument object, and execution errors passing through untouchedtool-registry.test.tscovering the path end to end, including that the tool does not run when validation failstypecheck,lintandoxfmtcleanFixes #2442
🤖 Generated with Claude Code
https://claude.ai/code/session_012AZp6uiHQb3WbsHWyQqG76
Generated by Claude Code