fix(tools): Agent.model — treat empty/whitespace as omitted + clearer…#11
Merged
Conversation
… schema doc
Observed today on real runs: both haiku and sonnet repeatedly emit
junk values in the Agent tool's `model` field — "", "default",
"sonnet", "claude" — because the JSON schema only said
"Optional model name override. Uses the same provider client." with
no hint about valid values or that omission is the correct default.
The empty-string case was the worst because zod rejected it with
"String must contain at least 1 character", surfaced as a tool error
and the model burned 2-3 tool-call retries before falling through to
direct grep/read.
Two fixes:
1. Zod schema: empty / whitespace-only strings normalize to
undefined via .transform(). The sub-agent then inherits the
parent's model, which is what the LLM meant 100% of the time.
Non-empty bogus values like "default" still pass schema and fail
loudly at the provider (model_not_found), which is the correct
blast radius — the parent agent observes the failure and can
pick a real ID or omit.
2. JSON schema description: explicit "OMIT this field unless you
explicitly need a different model" + valid example
(claude-sonnet-4-6, claude-haiku-4-5-20251001) + enumerated
anti-examples ("default", "sonnet", "claude", empty string).
Strongly nudges LLMs to skip the field instead of guessing.
Tests added in agent-tools.test.ts (4 cases): empty / whitespace /
tabs+newlines all parse as omitted, real model id still passes.
Local: 175 tests, 3/3 green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
… schema doc
Observed today on real runs: both haiku and sonnet repeatedly emit junk values in the Agent tool's
modelfield — "", "default", "sonnet", "claude" — because the JSON schema only said "Optional model name override. Uses the same provider client." with no hint about valid values or that omission is the correct default.The empty-string case was the worst because zod rejected it with "String must contain at least 1 character", surfaced as a tool error and the model burned 2-3 tool-call retries before falling through to direct grep/read.
Two fixes:
Zod schema: empty / whitespace-only strings normalize to undefined via .transform(). The sub-agent then inherits the parent's model, which is what the LLM meant 100% of the time. Non-empty bogus values like "default" still pass schema and fail loudly at the provider (model_not_found), which is the correct blast radius — the parent agent observes the failure and can pick a real ID or omit.
JSON schema description: explicit "OMIT this field unless you explicitly need a different model" + valid example (claude-sonnet-4-6, claude-haiku-4-5-20251001) + enumerated anti-examples ("default", "sonnet", "claude", empty string). Strongly nudges LLMs to skip the field instead of guessing.
Tests added in agent-tools.test.ts (4 cases): empty / whitespace / tabs+newlines all parse as omitted, real model id still passes.
Local: 175 tests, 3/3 green.