fix(anthropic): align Glob/Grep with the real Claude Code schema - #4
Open
st333in wants to merge 1 commit into
Open
fix(anthropic): align Glob/Grep with the real Claude Code schema#4st333in wants to merge 1 commit into
st333in wants to merge 1 commit into
Conversation
Claude Code rejects the whole tool call with
`InputValidationError: ... An unexpected parameter \`X\` was provided`,
and separately drops a case-insensitivity request without any error.
Four defects, one shared root cause.
## Defects
1. Grep leaked `explanation` outbound. Claude Code's Grep schema has no such
key. The former comment claimed "explanation is required", which the
official tools-reference contradicts.
2. Glob leaked `includeIgnoredFiles` outbound. Claude Code's Glob only takes
{pattern, path}; gitignore behaviour is controlled by the launch-time
CLAUDE_CODE_GLOB_NO_IGNORE environment variable, not a per-call parameter.
3. Grep *fabricated* `exclude` and `case_sensitive` outbound: the target key
names passed to remap_tool_keys do not exist in Claude Code's schema at all.
4. `-i` had both the wrong key name and inverted polarity, and failed
silently. Claude Code sends ripgrep-style `-i` (case *insensitive* = true),
while the inbound mapping only read `caseSensitive` (case *sensitive* =
true). The request to ignore case was discarded, the search ran
case-sensitive, and the empty result was indistinguishable from "the text
does not exist" - so the model concluded exactly that.
## Root cause and fix
`kiro_builtin_tool_schema` advertised Kiro-native shapes upstream and relied
on a bidirectional translation layer to restore Claude Code shapes on the way
back. Measured against a live proxy (custom client posting to a local
/v1/messages), Kiro **accepts and honours an arbitrary declared schema**:
advertise the Claude Code shape and the model emits the Claude Code shape
directly.
That makes the translation layer a net loss rather than a necessity:
- It is the sole source of the phantom parameters. Defects 1-3 are all
produced by it, and leaking a single one costs the entire tool call.
- It amputates real client capabilities. `path` (including the escape hatch
the official docs prescribe - pass a gitignored path directly to search
inside it), `type`, `output_mode`, `head_limit`, `multiline`, `-n` and
`-A/-B/-C` were never advertised, so the model had no way to know they
exist. In testing the model could only cram the directory into `glob`, and
since Grep respects .gitignore, node_modules stayed unreachable and
indistinguishable from an absent file.
Now the real Claude Code schema is advertised directly, the mapping collapses
to near-identity, and legacy key aliases plus an outbound allowlist remain as
a safety net. The allowlist encodes what is permitted rather than what is
forbidden, so a newly introduced phantom key is blocked automatically.
Exclusion needs no translation at all: Claude Code's `glob` is `rg --glob`, so
with the real schema advertised the model writes `!**/*.rs` on its own.
## One finding that only testing surfaced
The declared keys are `-i` and `-n`, but the model returns them **without the
leading dash** as `i` and `n`. This repo is not the culprit -
normalize_json_schema does not rewrite property names. Left alone this would
be the third recurrence of the same phantom-parameter failure, so the outbound
path now restores bare `i`/`n`/`A`/`B`/`C` to their dashed form.
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.
Summary
Four defects in the
Glob/Grepmapping between Claude Code and Kiro. Three of them make the client reject the entire tool call; the fourth fails silently, which makes it the dangerous one.explanationoutboundInputValidationError: ... An unexpected parameter \explanation` was provided` — the whole tool call is rejectedincludeIgnoredFilesoutboundexcludeandcase_sensitiveoutboundremap_tool_keysrenames into key names that do not exist in Claude Code's schema-ihas both the wrong key name and inverted polarityOn defect 4: Claude Code sends ripgrep-style
-i(case insensitive = true), while the inbound mapping only readcaseSensitive(case sensitive = true). Different name, opposite meaning, so the request to ignore case was dropped and the model drew the wrong conclusion from the empty result.The fix advertises Claude Code's real schema instead of translating, which collapses the mapping to near-identity and removes the whole class of bug.
Motivation and background
kiro_builtin_tool_schemaadvertises Kiro-native parameter shapes upstream, and a bidirectional layer then translates them back into Claude Code shapes. I measured this against a live proxy with a small client posting to a local/v1/messages, and Kiro accepts and honours an arbitrary declared schema: advertise the Claude Code shape and the model emits the Claude Code shape directly.That makes the translation layer a net loss rather than a necessity.
It is the only source of the phantom parameters. Defects 1–3 are all produced by it. This failure has now recurred twice in the wild —
explanationon Grep,includeIgnoredFileson Glob — and each occurrence costs the entire tool call, not just the offending argument.It amputates real client capability.
path,type,output_mode,head_limit,multiline,-nand-A/-B/-Cwere never advertised, so the model had no way to know they exist.pathmatters most: per the official tools reference, Grep respects.gitignore, and the prescribed way to search an ignored path is to pass that path directly. Withoutpaththat escape hatch is unreachable. In testing, the model could only cram the directory intoglob, andnode_modulesstayed invisible in a way it could not distinguish from an absent file.Exclusion needed no translation in the first place. Claude Code's
globmaps torg --glob, and ripgrep supports!negation, so once the real schema is advertised the model writes!**/*.rsunprompted.Schemas were taken from the official tools reference, Glob and Grep sections.
Type of change
Testing
cargo testpassescargo fmtappliedcargo clippyreports no new warningspnpm build(not applicable — no frontend changes)cargo test --no-default-features: 2288 passed / 0 failed.cargo clippy --no-default-features: no new warnings. Four warnings remain intool_compat.rsand are pre-existing, in code this PR does not touch:maybe_insert,remap_tool_keys, themap_tool_namedoc list, and the unusedrestore_tool_use_for_clientre-export.Beyond the unit tests, each scenario below was measured against a running proxy carrying this build, using the builtin tool names Claude Code actually sends. The assertion is that every returned key fits Claude Code's real schema:
tool_use.input{"pattern":"license","path":"admin-ui/node_modules","glob":"**/package.json","output_mode":"files_with_matches"}{"pattern":"usestate","-i":true,"-n":true,"head_limit":20,"output_mode":"content"}{"pattern":"TODO","glob":"!**/*.rs","output_mode":"files_with_matches"}{"pattern":"**/*.toml","path":"KiroStudio-src"}Zero phantom parameters across all four.
pathnow arrives, the dash on-i/-nsurvives, and exclusion comes out native.The existing regression test was extended to cover the outbound allowlist,
-ipolarity in both directions, the bare-ito-idash restoration, andpathno longer being dropped.Checklist
Additional notes
A finding worth flagging on its own. The declared keys are
-iand-n, but the model returns them without the leading dash, asiandn. This repository is not the cause —normalize_json_schemadoes not rewrite property names — so the dash is dropped either upstream or by the model. Left alone this would have been the third recurrence of the same phantom-parameter failure, so the outbound path now restores barei/n/A/B/Cto their dashed form. Worth knowing if any other dash-prefixed parameter is ever advertised.Scope. Limited to
GlobandGrep, the two tools whose schema I could verify end to end. The other six builtin tools keep the existing "preserve unmapped keys" contract, whichWrite.write_modedepends on and an existing test pins.Behavioural change to be aware of. This changes the schema surface the model sees, which changes the parameter shapes it generates. If there is a reason to keep the Kiro-native shape that I am not seeing — prompt cache stability, or parity with the reference implementation the comments mention — a narrower version that only strips the leaked keys is easy to derive from this one, though it would leave defect 4 and the missing
pathunfixed. Happy to split it that way.Verification I could not run.
CLAUDE.mdprescribes theskiapiDocker loop as the verification path. I do not have access to that host, so the numbers above come from a local build plus the live-proxy measurements.Note for anyone reproducing on Windows. With
core.autocrlf=true, seven tests fail locally that pass in CI. Theyinclude_str!their own source and search for LF-only literals such as"\n}\n", which cannot match in a CRLF working tree. The 2288/0 figure above is from an LF checkout, matching CI. Unrelated to this PR, but it costs time to rediscover.