OmniRoute Version
3.8.52 (npm global)
Installation Method
npm (npm i -g omniroute), bare-metal Linux
Operating System
Linux (Ubuntu)
Node.js Version
v22.23.1
Provider(s) Involved
kiro and ollama-cloud
Model(s) Involved
claude-opus-5.5, deepseek-v4.1-flash:1017
Client Tool
Kilo Code 7.8.15 (VS Code extension and CLI), using POST /v1/messages (sourceFormat: claude, targetFormat: openai)
Description
Since upgrading to 3.8.52, every read tool call made through OmniRoute comes back with the argument renamed from filePath to file_path. Kilo declares filePath as the required field, so it rejects the call with:
The read tool was called with invalid arguments: ["filePath"]: is missing and is required.The model then retries, producing an endless tool-call loop. This happens with every model, including real Claude models (claude-opus-5.5), not just DeepSeek.
Kilo is not the only affected client: any Anthropic-format client that does not use Claude Code's parameter names (Kilo, OpenCode, Cline, etc.) is broken by this.
Steps to Reproduce
-
Point Kilo (or any OpenCode-style client) at OmniRoute
POST /v1/messages. -
Send a request whose
tools[]declares:{ "name": "read", "input_schema": { "type": "object", "properties": { "filePath": { "type": "string" } }, "required": ["filePath"] } } -
Ask the model to read a file.
-
Observe the tool call that comes back:
content_block_start: {"type":"tool_use","name":"Read","input":{}} input_json_delta: {"file_path":"/tmp/test.txt"} -
The client rejects it because
filePathis absent, then retries.
Minimal replay (no client involved) with a captured real Kilo request:
curl -N http://localhost:20128/v1/messages \
-H 'content-type: application/json' \
-H 'x-api-key: <key>' \
-H 'anthropic-version: 2023-06-01' \
--data-binary @request.jsonrequest.json declares read + required:["filePath"]; the response still contains name:"Read" and {"file_path": ...}.
Expected Behavior
A tool-call argument rewrite must be reversed before the response reaches the client, or must not be applied at all when the client did not declare that parameter name. A client that declared filePath must receive filePath.
Actual Behavior
The response shim always converts filePath -> file_path and deletes the original key, regardless of the client.
Root Cause
open-sse/translator/helpers/toolCallShim.ts:48:
// Some models name the Read path `path` / `filePath`; Claude Code requires `file_path`.
function sanitizeFilePathArg(args: Record<string, unknown>): void {
if (typeof args.file_path === "string" && args.file_path) return;
const alias = [args.path, args.filePath].find((v) => typeof v === "string" && v);
if (!alias) return;
args.file_path = alias;
if (typeof args.path === "string") delete args.path;
if (typeof args.filePath === "string") delete args.filePath; // <- destroys the client's field
}It is registered as the Read shim (toolCallShim.ts:88) and resolveToolCallShim matches case-insensitively (toolCallShim.ts:114), so it also fires for Kilo's lowercase read. openai-to-claude.ts:938 applies it unconditionally on the response path.
Version bisect:
v3.8.50,v3.8.51:toolCallShim.ts= 4886 bytes, nosanitizeFilePathArg.v3.8.52: 5447 bytes,sanitizeFilePathArgadded.
/v1/messages is only the transport envelope; tool schemas are client-defined. The request side already knows how to distinguish Claude Code clients (executors/base.ts:991, x-app / user-agent), but the response shim does not use that check.
Suggested Fix
Preferred: apply the Claude Code alias only when the request actually came from a Claude Code client (same detection as executors/base.ts:991), otherwise leave the arguments untouched.
Minimal safe fallback: do not delete the original key - set file_path but keep filePath when it was supplied. Clients decode only the keys they declared, so both Claude Code and Kilo would work.
Related Issues
- #13942 - same class of bug (response-side rename leaking arg keys to clients), closed as not planned.
- #2695, #2889, #2935 - origin of the
Readshim (thepagescleanup), which is why the shim exists.
Test Impact
Needs a new unit test.
Error Logs / Output
The read tool was called with invalid arguments: ["filePath"]: is missing and is required.
Please rewrite the input so it satisfies the expected schema.Client-side tool-call record:
{
"tool": "read",
"state": {
"status": "error",
"input": { "file_path": "/home/user/repo/.projenrc.ts" },
"error": "The read tool was called with invalid arguments: [\"filePath\"]: is missing and is required."
}
}Additional Context
- Reproduced through a Kiro-hosted Claude Opus target (
claude-opus-5.5) and an ollama-cloud-hosted DeepSeek Flash target (deepseek-v4.1-flash), so it is model-agnostic. Local routing aliases are intentionally omitted; they are private configuration labels and irrelevant to the bug. - Current workaround for users: downgrade to
3.8.51, patch out thedelete args.filePathline, or normalizefile_path->filePathin the client.
Validation Plan
- Client declaring
filePathreceivesfilePathonPOST /v1/messages(streaming and non-streaming). - Claude Code client still receives
file_path(no regression). - Non-
Readtools and MCP tools are byte-identical (no shim applied). -
claude-opus-5.5(Kiro) anddeepseek-v4.1-flash(ollama-cloud) both complete a read without a retry loop.
Activity
0No activity yet.