fix: make tool invalid-arguments errors clearly actionable to the model#11961
fix: make tool invalid-arguments errors clearly actionable to the model#11961mvanhorn wants to merge 1 commit into
Conversation
| } | ||
| } | ||
|
|
||
| // kilocode_change start |
There was a problem hiding this comment.
WARNING: New Kilo-only logic added directly to a shared upstream file
formatter, format(), path(), and reason() are ~35 lines of pure Kilo-specific logic added inline to tool.ts, a shared opencode file. Per the Fork Isolation Rule in packages/opencode/AGENTS.md, Kilo-specific logic touching a shared upstream file should be extracted into a mirror file under src/kilocode/tool/tool.ts and called from here behind a single kilocode_change hook, rather than inlined with a kilocode_change start/end block. This keeps the upstream diff minimal for future merges — right now this whole block will conflict with any upstream change to the surrounding error-formatting code.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| } | ||
| const result = formatter(error.issue) | ||
| if (result.issues.length === 0) { | ||
| return String(error) |
There was a problem hiding this comment.
WARNING: Fallback can silently reintroduce the exact SchemaError(...) jargon this PR fixes
If Schema.isSchemaError(error) is true but the standard-schema formatter yields zero issues (e.g. a compound/union failure the formatter doesn't flatten to leaf issues), this falls back to String(error), which is the raw, model-unreadable text the PR is trying to eliminate. Since this is a real schema error (not just an unrecognized error shape), it may be worth a more generic-but-still-readable fallback (e.g. error.message) instead of the raw String(error) jargon.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| if (message.toLowerCase().includes("required")) { | ||
| return message | ||
| } | ||
| if (message === "Missing key") { |
There was a problem hiding this comment.
SUGGESTION: Exact string match on an internal library message is fragile
message === "Missing key" depends on an exact, undocumented string from Effect's SchemaIssue formatter. If a future Effect upgrade changes this wording even slightly (casing, punctuation, phrasing), this branch silently stops matching and the reason falls through to the raw internal message again — with no test failure unless the exact .toLowerCase().includes("missing")/"required" assertions catch it. Consider matching more defensively (e.g. message.toLowerCase().includes("missing")) so minor wording changes upstream don't regress the readability fix.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
Code Review SummaryStatus: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Reviewed by claude-sonnet-5-20260630 · Input: 24 · Output: 9.7K · Cached: 545.2K Review guidance: REVIEW.md from base branch |
Issue
Fixes #11391
Context
When a tool is called with arguments that fail its parameter schema, the model-facing error
detailwas the raw Effect schema failure string, e.g.SchemaError(Missing key at ["pattern"]). The reported case is a model callinggrepwithout the requiredpatternargument and getting backThe grep tool was called with invalid arguments: SchemaError(Missing key at ["pattern"]). Please rewrite the input so it satisfies the expected schema.TheSchemaError(...)fragment is internal jargon the model cannot act on, so it loops without self-correcting. This makes that detail readable and actionable.Implementation
The root cause is the
detailconstruction in thewrap()closure inpackages/opencode/src/tool/tool.ts:toolInfo.formatValidationError ? toolInfo.formatValidationError(error) : String(error). Built-in tools do not setformatValidationError, sodetailfell through toString(error)— the opaque parse-error text.The fallback now runs a shared formatter that renders one line per failing field as
<json-path>: <reason>(for example["pattern"]: is missing and is required). Because the fix lives at the tool boundary, every built-in tool without its ownformatValidationErrorhook gets the clearer message with no per-tool change. Decoding also runs witherrors: "all", so an input missing several required keys enumerates every offending field instead of stopping at the first. TheInvalidArgumentsErrorenvelope and its typed matchability are unchanged; only thedetailbody becomes readable.Note on API: the plan referenced Effect 3's
ParseResult.ArrayFormatter, but this repo ships Effect4.0.0-beta.66where that API is gone; the implementation uses the v4SchemaIssue.makeFormatterStandardSchemaV1()+Schema.isSchemaError.Screenshots
N/A — model-facing error text, no visual surface.
How to Test
Manual/local verification
bun test ./test/tool/tool-define.test.tsinpackages/opencode/: 7 pass, 0 fail (executed by the agent).bun run typecheckinpackages/opencode/: clean (executed by the agent).Reviewer test steps
packages/opencode/, runbun test ./test/tool/tool-define.test.ts.{}against apatternschema) surfaces["pattern"]with a human-readable missing/required reason and noSchemaError(substring; multiple missing fields enumerate every path; the existing nested-path regression (["questions"][0]["question"]) still holds.Blocked checks and substitute verification
Checklist
Get in Touch
Reachable via GitHub @mvanhorn.