claude CLI 2.1.215 emits tool_use with empty input in stream-json (tools never execute)
Bug Report: claude CLI 2.1.215 emits tool_use with empty input in stream-json (tools never execute)
Reported on behalf of a cc-suite / claude-octopus user. Root cause traced to the @anthropic-ai/claude-code CLI binary.
Environment
- OS: macOS (darwin)
claudeCLI: 2.1.215 (@anthropic-ai/claude-code)- Node: v22.23.1 (nvm)
- Also affects:
@anthropic-ai/claude-agent-sdk@0.2.141(bundled byclaude-octopus@1.2.0), since it drives the CLI viastream-json.
Summary
When the CLI is driven via --output-format stream-json, every tool_use block is emitted with "input": {}. The tool then fails validation (tool_result.is_error = true, "required parameter file_path is missing"), the agent loops, and the run ends at error_max_turns without performing any action. The same failure occurs in normal (non-stream-json) mode, so the empty input is produced before output serialization — not a stream-json-only artifact.
Steps to reproduce (self-contained, no SDK needed)
claude -p "Use the Write tool to create /tmp/repro_foo.txt containing exactly the word HI." \
--output-format stream-json --verbose --permission-mode acceptEdits --max-turns 3
Expected
tool_use.inputcarries the parameters the model produced (e.g.{ "file_path": "/tmp/repro_foo.txt", "content": "HI" }).- The Write tool executes and the file is created.
Actual
tool_use.inputis{}for every tool call.tool_resultreportsis_error: truewithInputValidationError(missingfile_path/content); no filesystem action occurs; run ends aterror_max_turns.
Raw stream-json evidence
Command:
claude --output-format stream-json --verbose --input-format stream-json \
--permission-mode acceptEdits --max-turns 3
# stdin (stream-json):
# {"type":"user","message":{"role":"user","content":"Use the Write tool to create /tmp/repro_foo.txt containing exactly the word HI."}}
Captured verbatim lines (loop repeats 3× with identical empty input):
{"type":"assistant","message":{"id":"msg_a783accd83ef5fc899917024","type":"message","role":"assistant","model":"claude-opus-4-8","content":[{"type":"tool_use","id":"call_00_LqNuN6TflHZEAHXBISGx4615","name":"Write","input":{}}],"stop_sequence":null,"context_management":null},"parent_tool_use_id":null,"session_id":"17a4ff84-ff6e-495d-82bc-757fa045ebd6","uuid":"af1efe2e-0c07-47c3-b873-0c8f894f32c3","timestamp":"2026-08-04T07:53:51.394Z"}
{"type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"<tool_use_error>InputValidationError: Write failed due to the following issues:\nThe required parameter `file_path` is missing\nThe required parameter `content` is missing</tool_use_error>","is_error":true,"tool_use_id":"call_00_LqNuN6TflHZEAHXBISGx4615"}]},"parent_tool_use_id":null,"session_id":"17a4ff84-ff6e-495d-82bc-757fa045ebd6","uuid":"2c706d20-a530-4019-848d-d9cf5faac172","timestamp":"2026-08-04T07:53:51.398Z","tool_use_result":"InputValidationError: [\n {\"expected\":\"string\",\"code\":\"invalid_type\",\"path\":[\"file_path\"],\"message\":\"Invalid input: expected string, received undefined\"},\n {\"expected\":\"string\",\"code\":\"invalid_type\",\"path\":[\"content\"],\"message\":\"Invalid input: expected string, received undefined\"}\n]"}
{"type":"result","subtype":"error_max_turns","duration_ms":10902,"is_error":true,"num_turns":4,"stop_reason":"tool_use","session_id":"17a4ff84-ff6e-495d-82bc-757fa045ebd6","total_cost_usd":0.004875,"errors":["Reached maximum number of turns (3)"]}
Note the tool_result proves the parameters are genuinely absent (received undefined), not merely omitted from display.
Suspected location (from inspecting the shipped 2.1.215 binary — to be confirmed by maintainers)
The CLI builds tool_use.input by accumulating the model stream's input_json_delta partial JSON, in a handler roughly of the form:
case "input_json_delta":
if (n && Ial(n)) { // Ial = tool_use type guard
let o = n[wal] || ""; // wal = hidden field holding accumulated partial_json
o += t.delta.partial_json;
let i = { ...n };
if (o) try { i.input = parse(o) } catch {}
r.content[t.index] = i;
}
The stream-json output formatter then relays message.content[].tool_use.input verbatim (e.g. yield { type:"assistant", message:n.message, ... }), so it does not strip input. The emptiness therefore originates in the accumulation step — likely the Ial(n) guard / hidden wal field not matching this build's internal tool_use block shape, so partial_json never lands in input.
Impact
Any caller driving the CLI through stream-json (including the official @anthropic-ai/claude-agent-sdk, and thus claude-octopus / cc-suite's claude_code MCP tool) receives empty tool inputs and cannot execute tools.
Notes
- Not an auth/permission issue:
--permission-mode acceptEditsis set; the failure ismissing parameters, not a denial. - Model observed:
claude-opus-4-8. - Happy to provide the minimal
@anthropic-ai/claude-agent-sdkquery()repro or additional captures if useful.
Minimal @anthropic-ai/claude-agent-sdk repro (alternative entry point)
import { query } from "@anthropic-ai/claude-agent-sdk";
const q = query({
prompt: 'Use the Write tool to create /tmp/repro.txt with content HI.',
options: { cwd: "/tmp", permissionMode: "acceptEdits", maxTurns: 4 },
});
for await (const m of q) {
if (m.type === "tool_use") console.log("name=", m.name, "input=", JSON.stringify(m.input));
// -> name= Write input= {} (repeated, then error_max_turns)
}This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗