You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
anthropic: intermittent 400 "tool_use.input: Input should be an object" — tool input sent as raw string right after tool result #53865
Intermittently a request fails with Anthropic 400 invalid_request_error: messages.N.content.M.tool_use.input: Input should be an object. Happened twice in one long tool-heavy session (04:44:49Z and 04:45:13Z; request_ids req_011CfpAGFPT8Vh6jWAmPTY15 / req_011CfpAJ1EiACT7zjg5zAtsM). Each failed attempt is persisted as an empty assistant message with error.type="provider.invalid-request"; the next retry a few seconds later succeeds unchanged (self-heals).
Evidence points to a race: the follow-up request is fired right after a tool result is ready but before the previous assistant message is finalized.
Capturing the outgoing request body shows exactly one tool_use per failing request whose input is a raw JSON string instead of an object — always the freshest tool call (first a write, then a shell). All other tool_use blocks in the same request are proper objects.
All tool inputs in the persisted session store are objects, so history is clean; the corruption exists only in the outgoing payload.
The assistant rows in question were finalized (time_updated) a few seconds after the failing request was sent (created 04:44:45Z / 04:45:10Z, failed request 04:44:49Z / 04:45:13Z, finalized 04:45:00Z / 04:45:16Z). The tool_result was already present in the request.
The bad index moved from messages.58 to messages.69, tracking history growth — so it is the newest tool call, not a fixed poisoned message.
The malformed input was already present in the request body as sent by opencode (captured at my self-hosted relay's inbound log; the relay is pure pass-through for this path).
Plugins
opencode.browser, openchamber-agent-tool
OpenCode version
2.0.22
Steps to reproduce
Long session with many tool calls, Anthropic provider.
A step ends with a tool call (observed with write and shell).
The next request is dispatched immediately after the tool result is ready.
Occasionally the request goes out with the previous tool_use.input still as a raw JSON string -> API 400. Not deterministic (timing race); observed 2x within one minute in one session.
Possibly related (same family, different layer/symptom): #36766, #36421, #47902.
Screenshot and/or share link
N/A — exact error text and request_ids included in the description above.
Thanks for the detailed report and the request IDs. I tried to reproduce this on 2.0.22 and couldn't.
I pointed the built-in Anthropic provider at a local mock of the Anthropic Messages streaming API. The mock logs every request body and rejects any tool_use.input that isn't an object, with the same 400 you saw. Then I ran a session with over 60 back-to-back steps alternating write and shell tool calls, each request sent right after the previous tool result. It covered tool input streamed in small pieces, input sent all at once, and two tool calls in a single step. Every outgoing tool_use.input was an object.
From the code, by the time a tool has a tool_result, its saved input is always an object (a non-object would be saved wrapped as { "value": ... }). That matches what you found in your session store. So a request with a tool_result but a raw-string input doesn't look like something opencode builds by itself. One thing that can change the outgoing messages is plugins: they can rewrite the message list just before the request is sent.
These would help narrow it down:
Does it still happen with opencode.browser and openchamber-agent-tool disabled? In particular, does either plugin register a session context hook?
Does it happen when connecting straight to Anthropic, without the relay, or with plain opencode instead of the OpenChamber wrapper?
If you can capture it again, please share the full failing request (redacted), not just the bad block. Server logs from around that time at OPENCODE_LOG_LEVEL=DEBUG would also help.
Possibly related: #51452 (also a string sent where an object is expected, though on the Mistral provider when a pending step is replayed after a service restart).
Could you retest on 2.0.25? It includes #53685. In 2.0.22, a tool call whose arguments never finished could stay in history with its raw argument text, and the next request would send that text as a string tool_use.input. That gives exactly this 400. In 2.0.25, those calls are closed out as failed, with input: {} and an error tool result.
If it still happens on 2.0.25, please share:
the full failing request body (redacted), especially whether the bad input is empty or partial JSON, and whether there's a tool_result with the same id
whether more than one OpenCode server or client was attached to that session at the time
I'm closing this for now because there hasn't been a reply to the questions above. I couldn't reproduce the 400 tool_use.input: Input should be an object on 2.0.22. Across 60+ consecutive write/shell steps against an Anthropic mock, every tool_use.input went out as an object. Without more details, I can't tell whether the string input comes from opencode, a plugin or the relay.
If it happens again, please comment here (or open a new issue) with:
whether it still happens with opencode.browser and openchamber-agent-tool disabled
whether it happens when connecting straight to Anthropic (no relay), or with plain opencode instead of the OpenChamber wrapper
the full failing request (redacted), and OPENCODE_LOG_LEVEL=DEBUG server logs from around that time
Still happening on 2.0.29 (includes #53685): Anthropic 400 messages.N.content.1.tool_use.input: Input should be an object. Unlike the original report it does not self-heal here: once a subagent session hits it, every following request in that session fails the same way, so the session is dead and has to be replaced. It hit three background subagent sessions within 15 minutes, including one created fresh after the others had failed.
Environment
opencode version: server 2.0.29 (latest); opencode CLI on PATH reports 1.18.30 (Homebrew), the server is the 2.0.29 build
OS: Darwin 25.5.0 arm64 (macOS)
Terminal: n/a (sessions driven from the OpenChamber web UI)
Shell: /bin/zsh
Install/channel: latest
Active plugins: opencode.browser, openchamber-agent-tool, two local plugins (employer-layer, session-keepalive, session-title.ts), @ex-machina/[email protected]
Model: anthropic/claude-opus-5-5 via the anthropic-auth plugin
Reproduction
Not minimal yet. Observed pattern:
A parent session spawns several background subagents (general) that run long shell commands (loops with sleep 60, timeouts up to 50 min) while other subagents run in parallel.
The opencode server restarts once (2026-10-14 ~11:47Z).
Within minutes, subagent requests fail with the 400 above. Resuming the same session (new prompt) fails again immediately with the same error (index moves with history).
A brand-new subagent session started after the restart failed the same way after ~20 messages.
Expected Behavior
Tool inputs are always sent as objects; a single malformed tool call does not poison the session.
Actual Behavior
Log (~/.local/share/opencode/log/opencode.log), level=ERROR Failed to drain Session:
11:54:22Z messages.94.content.1.tool_use.input: Input should be an object (session A)
11:57:19Z messages.94 … (session A, after resume)
11:58:29Z / 12:00:06Z messages.152 / messages.116 … (session B, after resume)
12:05:21Z / 12:05:55Z messages.42 … (session C, created 11:55Z, fresh)
12:08:37Z …
The last persisted part in session C is a shell tool call with "executed":false, "status":"running" and a proper object input (a 40× sleep 60 polling loop with timeout: 3000000). The persisted history contains no non-object input, so — as in the original report — the string seems to exist only in the outgoing payload. I did not capture the outgoing request body.
Additional Context
Frequency: 3 of 3 background subagents running at that time; 7 error lines in 15 minutes. The parent session (same server) kept working.
Common factor in all three: a long-running shell call (polling loop, large timeout) was in flight; two of three were in flight across the server restart.
Workaround: replace the session with a new one and re-brief it from files on disk.
Activity
opencode-agent commentedon Oct 8, 2026
Thanks for the detailed report and the request IDs. I tried to reproduce this on 2.0.22 and couldn't.
I pointed the built-in Anthropic provider at a local mock of the Anthropic Messages streaming API. The mock logs every request body and rejects any
tool_use.inputthat isn't an object, with the same 400 you saw. Then I ran a session with over 60 back-to-back steps alternatingwriteandshelltool calls, each request sent right after the previous tool result. It covered tool input streamed in small pieces, input sent all at once, and two tool calls in a single step. Every outgoingtool_use.inputwas an object.From the code, by the time a tool has a
tool_result, its saved input is always an object (a non-object would be saved wrapped as{ "value": ... }). That matches what you found in your session store. So a request with atool_resultbut a raw-string input doesn't look like something opencode builds by itself. One thing that can change the outgoing messages is plugins: they can rewrite the message list just before the request is sent.These would help narrow it down:
opencode.browserandopenchamber-agent-tooldisabled? In particular, does either plugin register a sessioncontexthook?opencodeinstead of the OpenChamber wrapper?OPENCODE_LOG_LEVEL=DEBUGwould also help.Possibly related: #51452 (also a string sent where an object is expected, though on the Mistral provider when a pending step is replayed after a service restart).
opencode-agent commentedon Oct 8, 2026
Could you retest on 2.0.25? It includes #53685. In 2.0.22, a tool call whose arguments never finished could stay in history with its raw argument text, and the next request would send that text as a string
tool_use.input. That gives exactly this 400. In 2.0.25, those calls are closed out as failed, withinput: {}and an error tool result.If it still happens on 2.0.25, please share:
inputis empty or partial JSON, and whether there's atool_resultwith the same idopencode-agent commentedon Oct 11, 2026
I'm closing this for now because there hasn't been a reply to the questions above. I couldn't reproduce the 400
tool_use.input: Input should be an objecton 2.0.22. Across 60+ consecutivewrite/shellsteps against an Anthropic mock, everytool_use.inputwent out as an object. Without more details, I can't tell whether the string input comes from opencode, a plugin or the relay.If it happens again, please comment here (or open a new issue) with:
opencode.browserandopenchamber-agent-tooldisabledopencodeinstead of the OpenChamber wrapperOPENCODE_LOG_LEVEL=DEBUGserver logs from around that timeThanks again for the detailed report.
TheBurgerShot commentedon Oct 14, 2026
Retested as asked: still happens on 2.0.29.
Summary
Still happening on 2.0.29 (includes #53685): Anthropic
400 messages.N.content.1.tool_use.input: Input should be an object. Unlike the original report it does not self-heal here: once a subagent session hits it, every following request in that session fails the same way, so the session is dead and has to be replaced. It hit three background subagent sessions within 15 minutes, including one created fresh after the others had failed.Environment
opencodeCLI on PATH reports 1.18.30 (Homebrew), the server is the 2.0.29 buildReproduction
Not minimal yet. Observed pattern:
general) that run long shell commands (loops withsleep 60, timeouts up to 50 min) while other subagents run in parallel.Expected Behavior
Tool inputs are always sent as objects; a single malformed tool call does not poison the session.
Actual Behavior
Log (
~/.local/share/opencode/log/opencode.log), level=ERRORFailed to drain Session:The last persisted part in session C is a
shelltool call with"executed":false,"status":"running"and a proper objectinput(a 40×sleep 60polling loop withtimeout: 3000000). The persisted history contains no non-objectinput, so — as in the original report — the string seems to exist only in the outgoing payload. I did not capture the outgoing request body.Additional Context
shellcall (polling loop, largetimeout) was in flight; two of three were in flight across the server restart.