Skip to content

server: drain replay of pending step fails with Mistral 400 ('str object' has no attribute 'items') and retries every ~15s #51452

Open

Description

@Phototonic

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

  1. Run a long-lived orchestrator session on provider mistral (native integration @opencode/ai/providers/mistral), model zai-glm-5-3 (variant high), accumulating a large reasoning/tool-call history (~973k cumulative input tokens, ~165k output, heavy tool + reasoning usage over several days).
  2. 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).
  3. On server start, SessionRunner.drain resumes the pending step and Mistral rejects the request with HTTP 400.
  4. 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, providerState item 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

Activity

  1. github-actions commented on Sep 26, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. Phototonic commented on Sep 26, 2026

    @Phototonic
    Author

    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:

    1. Trigger: SessionRunner.drain resuming 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).
    2. Provider stack: native Mistral integration (@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 bare invalid_request_error.
    3. Currency: this still reproduces on server v2.0.18 (latest release). Web UI session stuck for hours with upstream invalid_request_error: no diagnostics, endless retry, no reliable recovery #45456 was closed as completed in August, so this is evidence that the replay-sanitization gap persists into the current release on the drain path / for this provider's state shape.

    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.error surfacing 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.

  3. satyalyadav commented on Oct 15, 2026

    @satyalyadav

    I observed the same drain-error message on OpenCode server 2.0.29 with provider/model asu/glm-5-3, variant high.

    At 2026-10-15T03:01:40.412Z, the server logged this error. Stack abridged:

    message="Failed to drain Session" cause="AI.Error: {\"error\":{\"message\":\"'str object' has no attribute 'items'\",\"type\":\"BadRequestError\",\"param\":null,\"code\":400}}"
        at SessionRunner.runStep (…)
        at SessionRunner.drain (…)
        at ServerProcess.start (…)
    

    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.

  4. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    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:

    • An export of the affected session, with anything private removed, mainly the last assistant message and the pending step. Check whether any tool call there has a string input rather than an object.
    • Whether you see Failed to drain Session without any further restarts, or only when the server starts or something else triggers a drain.

    Related issues:

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