Skip to content

fix: make tool invalid-arguments errors clearly actionable to the model#11961

Open
mvanhorn wants to merge 1 commit into
Kilo-Org:mainfrom
mvanhorn:fix/11391-tool-invalid-args-error
Open

fix: make tool invalid-arguments errors clearly actionable to the model#11961
mvanhorn wants to merge 1 commit into
Kilo-Org:mainfrom
mvanhorn:fix/11391-tool-invalid-args-error

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Issue

Fixes #11391

Context

When a tool is called with arguments that fail its parameter schema, the model-facing error detail was the raw Effect schema failure string, e.g. SchemaError(Missing key at ["pattern"]). The reported case is a model calling grep without the required pattern argument and getting back The grep tool was called with invalid arguments: SchemaError(Missing key at ["pattern"]). Please rewrite the input so it satisfies the expected schema. The SchemaError(...) 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 detail construction in the wrap() closure in packages/opencode/src/tool/tool.ts: toolInfo.formatValidationError ? toolInfo.formatValidationError(error) : String(error). Built-in tools do not set formatValidationError, so detail fell through to String(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 own formatValidationError hook gets the clearer message with no per-tool change. Decoding also runs with errors: "all", so an input missing several required keys enumerates every offending field instead of stopping at the first. The InvalidArgumentsError envelope and its typed matchability are unchanged; only the detail body becomes readable.

Note on API: the plan referenced Effect 3's ParseResult.ArrayFormatter, but this repo ships Effect 4.0.0-beta.66 where that API is gone; the implementation uses the v4 SchemaIssue.makeFormatterStandardSchemaV1() + Schema.isSchemaError.

Screenshots

N/A — model-facing error text, no visual surface.

before after
N/A N/A

How to Test

Manual/local verification

  • bun test ./test/tool/tool-define.test.ts in packages/opencode/: 7 pass, 0 fail (executed by the agent).
  • bun run typecheck in packages/opencode/: clean (executed by the agent).

Reviewer test steps

  1. In packages/opencode/, run bun test ./test/tool/tool-define.test.ts.
  2. Confirm the new cases pass: a missing required scalar ({} against a pattern schema) surfaces ["pattern"] with a human-readable missing/required reason and no SchemaError( substring; multiple missing fields enumerate every path; the existing nested-path regression (["questions"][0]["question"]) still holds.
  3. Confirm valid input still decodes and executes, so the formatter only runs on the failure branch.

Blocked checks and substitute verification

  • None. The full repo test suite was not run; substitute verification was the targeted package tests and typecheck above, which cover the changed boundary.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

Get in Touch

Reachable via GitHub @mvanhorn.

}
}

// kilocode_change start

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@kilo-code-bot

kilo-code-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/tool/tool.ts 34 New Kilo-only formatting logic added inline to a shared upstream file instead of a src/kilocode/ mirror
packages/opencode/src/tool/tool.ts 43 Empty-issues fallback can silently reintroduce the raw SchemaError(...) jargon this PR fixes

SUGGESTION

File Line Issue
packages/opencode/src/tool/tool.ts 62 Exact string match on an internal Effect message ("Missing key") is fragile to library upgrades
Files Reviewed (3 files)
  • .changeset/tool-invalid-args-actionable-error.md - no issues
  • packages/opencode/src/tool/tool.ts - 3 issues
  • packages/opencode/test/tool/tool-define.test.ts - no issues

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 main

@johnnyeric johnnyeric requested a review from chrarnoldus July 6, 2026 12:56
@chrarnoldus chrarnoldus self-assigned this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants