Repository navigation
Description
Summary
After upgrading to OpenCode v2, long-running sessions on the native Mistral provider (model zai-glm-5-3) fail on SessionRunner.drain (service-start resume of a pending step) with a provider 400: 'str object' has no attribute 'items'. Normal sends to the same session and model succeed — only the drain-path replay of the pending step fails. The drain then retries the identical payload roughly every 15 seconds, producing error spam with no self-recovery, re-triggering at each service restart.
Environment
- opencode version: server 2.0.18 (
@opencode/cli, bun global, latest release); client 2.0.16 (OpenChamber bundled CLI) - OS: Pop!_OS 24.04, Linux 7.1.5-76070105-generic x86_64
- Terminal: OpenChamber desktop app (embedded TUI); TERM=linux
- Shell: /bin/bash
- Install/channel: release channel (bun global) + OpenChamber v2 AppImage bundle
- Active plugins: oh-my-opencode-slim@latest (2.2.25), dcg-guard.js (local, V2 format), openchamber-agent-tool (local); opencode-mem configured but fails to load (pre-V2 release). None of these shape the failing request path (verified).
Reproduction
- Run a long-lived orchestrator session on provider
mistral(native integration@opencode/ai/providers/mistral), modelzai-glm-5-3(varianthigh), accumulating a large reasoning/tool-call history (~973k cumulative input tokens, ~165k output, heavy tool + reasoning usage over several days). - Leave a step pending in the session queue across a service restart (in our case: a queued continuation after a background child-task completion, then
opencode service restart). - On server start,
SessionRunner.drainresumes the pending step and Mistral rejects the request with HTTP 400. - The drain retries the same request every ~15s. A second, smaller session hit the same error once at the same restart and then continued normally — the failure is specific to the pending-step replay, not to the session or model generally.
Expected Behavior
- Drain resume should send a request the provider accepts, or sanitize/refresh the replayed provider state (reasoning/thinking blocks,
providerStateitem references) so stale state cannot poison the replay. - A non-transient provider 4xx should not be retried indefinitely; back off and surface a clear, user-visible error instead.
- The outgoing request body should be logged at DEBUG on provider errors (cf. [FEATURE]: Log LLM API request/response body at DEBUG log level #29186) so the rejected field can be identified — the provider does not disclose which field was invalid, and opencode's logs do not include the body.
Actual Behavior
timestamp=2026-09-26T05:17:33.602Z level=ERROR run=65950548 message="Failed to drain Session" cause="AI.Error: ThirdPartyException - {"error":{"message":"'str object' has no attribute 'items'","type":"BadRequestError","param":null,"code":400}}
at SessionStep.attempt ... at SessionRunner.runStep ... at SessionRunner.drain ... at ServerProcess.start" role=server sessionID=ses_f2456a4b7ffe0kfLCDXgyzSNYp
Retries of the identical error: 05:17:33, 05:19:45, 05:20:03, 05:21:29, 05:21:43, 05:21:57, 05:22:05, 05:22:24, 05:23:12 (~15s intervals, 13+ occurrences over ~7 minutes) before stopping on its own. The same failure recurred across consecutive service restarts.
Additional Context
- The error text is a Python-side error from Mistral's GLM backend: a string arrived where a dict/object was expected. This is consistent with replayed reasoning/thinking state being serialized in a shape the provider rejects on the drain path, while the normal send path (which works against the same model) does not reproduce it.
- Not plugin-related: the only provider-shaping plugin was inert during the first occurrence and only ever targeted a different model (
zai-glm-5-2) on a different, OpenAI-compatible provider definition; the failing sessions ran plain native-integrationzai-glm-5-3. - First observed immediately after upgrading to v2 (OpenChamber v2 bundle); the same multi-day workflow under v1 did not exhibit it.
- Related issues: Web UI session stuck for hours with upstream invalid_request_error: no diagnostics, endless retry, no reliable recovery #45456 (stuck drain retry loop on long sessions; replayed
providerState/reasoning state rejected upstream; same "no request body in logs" complaint), compaction sends mutated thinking block -> permanent 400 retry loop #39291 (mutated thinking block → permanent 400 retry loop), Failed parallel tool call leaves dangling tool_call_id, causing 400 errors on all subsequent requests #44852 (danglingtool_call_idpoisoning replays), [FEATURE]: Log LLM API request/response body at DEBUG log level #29186 (log request/response body at DEBUG). - Workarounds tried: none effective server-side; the loop eventually pauses but re-triggers on the next restart. Upstream report Web UI session stuck for hours with upstream invalid_request_error: no diagnostics, endless retry, no reliable recovery #45456 indicates recovery requires manual session surgery/revert.
Activity
github-actions commentedon Sep 26, 2026
This issue might be a duplicate of existing issues. Please check:
Phototonic commentedon Sep 26, 2026
Checked all four candidates — confirming the relationship of each rather than plain duplicate status:
Same root-cause family as #39291 / #45456 (replayed reasoning/thinking state rejected by the provider on request rebuild), but this report differs on three axes:
SessionRunner.drainresuming a pending step after a service restart — not compaction (compaction sends mutated thinking block -> permanent 400 retry loop #39291) and not a generic long-running-overflow case (Web UI session stuck for hours with upstream invalid_request_error: no diagnostics, endless retry, no reliable recovery #45456).@opencode/ai/providers/mistral,zai-glm-5-3) with a Python-side backend error —'str object' has no attribute 'items', i.e. a string arriving where a dict/object is expected — rather than Anthropic's byte-identical-thinking rejection or a bareinvalid_request_error.Not a duplicate of #46564 — that tracks the context-overflow classification gap (opaque 400 never engages one-shot compaction recovery). In our case normal sends carrying the same full session history succeed; only the drain-path replay is rejected, so it is not an overflow misclassification.
Not a duplicate of #49740, but complementary — that issue tracks the missing
session.errorsurfacing for swallowed drain exceptions. Our case is another concrete instance of that silent dead-end (the client only sees repeated failures), but the cause analysis here is distinct. The retry cadence evidence (~15s, 13+ occurrences over ~7 minutes) is available for that report if useful.Suggest keeping this open as the drain-path + native-Mistral data point unless maintainers prefer to consolidate under #39291 — in which case the two deltas worth carrying over are the drain-resume trigger and the v2.0.18 reproduction.
satyalyadav commentedon Oct 15, 2026
I observed the same drain-error message on OpenCode server 2.0.29 with provider/model
asu/glm-5-3, varianthigh.At
2026-10-15T03:01:40.412Z, the server logged this error. Stack abridged:The session metadata currently reports
outcome: failed, although completed shell calls appear in the log after the error.I found only one matching drain-error record for this session, so I cannot confirm the repeated-retry behavior described here. This adds an occurrence with a different configured provider; it does not establish a provider-independent cause.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. We can't reproduce this in our sandbox. The 400 comes from Mistral's GLM backend, and seeing it needs a real Mistral account with
zai-glm-5-3, which we don't have here. A mocked 400 wouldn't show whether the request replayed on startup actually differs from a normal send.One guess, not confirmed: the Mistral integration sends tool-call arguments as a JSON-encoded copy of the stored input. If a tool call in the pending step stored its input as a plain string rather than an object, the backend would get a string where it expects an object, which would match
'str object' has no attribute 'items'.If you can, these would help a maintainer with Mistral access:
inputrather than an object.Failed to drain Sessionwithout any further restarts, or only when the server starts or something else triggers a drain.Related issues:
tool_call_idbreaking replays