Skip to content

[bug] openai-compatible (custom provider): intermittent 400 "Assistant tool call function.arguments must be a JSON object" — freshest tool-call arguments sent non-object right after tool result (self-heals on retry) #54025

Closed as not planned

Description

@JustinKeltner

Description

Summary

On a custom OpenAI-compatible provider (@ai-sdk/openai-compatible), a long tool-heavy session intermittently fails with:

{"error": {"message": "Assistant tool call function.arguments must be a JSON object.", "type": "invalid_request_error", "param": null, "code": null}}

Twice in one ~37-minute session (~90 assistant steps, dozens of tool calls), each failure self-heals on the next retry seconds later with unchanged history. This looks like the openai-compatible flavor of #53865 (anthropic: tool_use.input: Input should be an object).

Evidence (from session store + server log, opencode.db inspected read-only)

  • Both failing requests went out in the same second the freshest assistant tool-calls message was finalized (message time_updated == failing request timestamp: 22:02:21Z and 22:02:59Z) — the same race signature as anthropic: intermittent 400 "tool_use.input: Input should be an object" — tool input sent as raw string right after tool result #53865.
  • Every tool-call part stored in the session has a proper JSON-object state.input (verified across the whole session, including missing-key edge cases). Stored history is clean; the corruption exists only in the outgoing payload — consistent with the follow-up request being built while the freshest tool call is mid-finalization/mid-argument-accumulation.
  • Both failures persisted as empty assistant messages with error.type="provider.invalid-request", status 400.
  • Server log shows Failed to drain Session with AI.Error.InvalidRequest wrapping the provider 400, once per failure.
  • In the installed 2.0.26 bundle, the openai-compatible chat conversion sends function:{name, arguments:JSON.stringify(input)}, and the streaming path starts a tool call with arguments:"" and appends raw text deltas (toolInputStart/toolInputDelta) until parse — so a request built mid-accumulation/finalization would carry raw argument text, which our provider's strict validator rejects with exactly this message.

Details requested in #53865 thread

  • Bad input shape: not captured on the wire (no relay on this path); inferred non-object/invalid from the provider's "must be a JSON object" validation wording.
  • More than one client/server attached: possibly yes — this desktop runs multiple opencode server processes against one ~/.local/share/opencode/opencode.db (one current 2.0.26 on :49374 serving the session + three older/stale processes). Happy to test with all but one stopped if that helps narrow it.
  • Affected provider is strict about tool_calls[].function.arguments parsing to a JSON object (strings rejected). Other providers used in the same period (incl. anthropic-format and another OpenAI-compatible endpoint) logged zero such errors.

Impact

Session-generation requests fail hard until a retry lands outside the race window; with queued/drain-style runners this can surface as "failed to send message" in clients. No data loss (history is intact), but it interrupts unattended runs.

Happy to capture the outgoing request body via a pass-through relay if that would help — tell me what to log and I'll reproduce.

Plugins

[email protected], @openchamber/[email protected], @openchamber/[email protected], local see-image.js

OpenCode version

2.0.26 (npm @opencode/cli; bun standalone server)

Steps to reproduce

  1. Custom provider in opencode.json(c): provider.inco with npm: @ai-sdk/openai-compatible, options.baseURL pointing at the provider's OpenAI-compatible endpoint (host redacted) — GLM-5.3-flash-class model whose API strictly requires tool_calls[].function.arguments to parse to a JSON object.
  2. Run a long agentic session (30+ min, ~90 assistant steps, shell/edit tools, 2-tool-call steps) so tool results flow back continuously.
  3. Observe: occasional 400 invalid_request_error: Assistant tool call function.arguments must be a JSON object. on the request immediately following a tool-calls step — timestamp coincides with the freshest assistant message's finalization second.
  4. Next attempt (retry/queued message) succeeds with unchanged history; session continues normally.

Screenshot and/or share link

No response

Operating System

Linux — Debian GNU/Linux 13 (trixie) desktop VM (client + server same host)

Terminal

OpenChamber web client attached to the background service

Activity

  1. opencode-agent commented on Oct 8, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I tried to reproduce this on 2.0.26 and couldn't.

    I set up a custom provider with @ai-sdk/openai-compatible, pointed at a local mock of the chat completions streaming API. The mock rejects any assistant tool_calls[].function.arguments that doesn't parse to a JSON object, with the same 400 you saw. Then I ran:

    • a 60-step session alternating shell and write calls, including steps with two tool calls, with arguments streamed in small pieces, all at once, or with the finish signal right after the last piece
    • unusual streaming shapes: full arguments in the first chunk, the id repeated on every chunk, and truncated or string-encoded arguments
    • three OpenCode processes running sessions at the same time on one opencode.db, including a run with slower streaming and one slower tool per step

    Across roughly 320 requests, every outgoing arguments value was a JSON object. Malformed model output was turned into an error tool result rather than being sent back raw.

    What would help narrow it down:

    • The failing request body. Your offer of a pass-through relay would be the most useful thing here. For the rejected request, please share the bad tool_calls[].function.arguments value, redacted. In particular: is it empty, partial JSON, a quoted JSON string, or an array? Is there a role: "tool" message with the same tool_call_id right after it?
    • Plugins. Does it still happen with @openchamber/opencode-cursor, @openchamber/opencode-claude and the see-image plugins disabled? Some of these hook into requests and could change the tool-call input.
    • Single server. Does it still happen with only the 2.0.26 server running, and the older servers on the same opencode.db stopped?

    Related: #53865 is the same symptom on the Anthropic provider (tool_use.input: Input should be an object).

  2. rekram1-node commented on Oct 9, 2026

    @rekram1-node
    Collaborator

    Are you positive it was on 2.0.26 and not an earlier version? Can u still reproduce?

  3. opencode-agent commented on Oct 11, 2026

    @opencode-agent
    Contributor

    Closing this for now, since we haven't heard back. I couldn't reproduce the 400 on 2.0.26: across roughly 320 requests to a strict openai-compatible mock, every outgoing tool_calls[].function.arguments was a JSON object. Without the failing request body there isn't enough to go on.

    If it happens again, please reopen or comment here with:

    • the rejected arguments value from the failing request (redacted), captured through a pass-through relay
    • whether it still happens with the OpenChamber and see-image plugins disabled
    • whether it still happens with only the 2.0.26 server running against opencode.db

    #53865 tracks the same symptom on the Anthropic provider, so it's worth watching too. Thanks again for the detailed report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions