Skip to content

[Bug] MiniMax-M3 (reasoning model) silently finishes stop and produces no text/tool parts in OpenCode 1.18.18 #43029

Closed as not planned

Description

@mannex111

[Bug] MiniMax-M3 (reasoning model) silently finishes stop and produces no text/tool parts in OpenCode 1.18.18

Summary

When using MiniMax-M3 (provider minimax-cn-coding-plan, a reasoning-capable model via OpenAI-compatible /v1/chat/completions), OpenCode 1.18.18 sometimes saves an assistant message with finish: "stop", tokens counted, only a reasoning part and no text/tool parts at all, and then waits for the next user message. From the UI perspective the model "thinks and stops" with no visible reply and no tool calls.

The same task works perfectly with deepseek-v4-flash (also a reasoning-capable model through the OpenAI-compatible layer) under the same configuration.

This looks like a sibling of #42677 (and #42300, #42287): OpenCode's stream→parts materializer is dropping parts when the upstream model returns reasoning content embedded inside delta.content rather than as a separate delta.reasoning_content field.

Environment

  • OpenCode: 1.18.18 (native Tauri build)
  • OS: WSL2 (Ubuntu 24.04), Linux x86_64
  • Provider: minimax-cn-coding-plan (OpenAI-compatible, https://api.minimaxi.com/v1/chat/completions)
  • Model: MiniMax-M3 (advertised as reasoning-capable; OMO bundled snapshot marks reasoning: true)
  • Plugin: oh-my-openagent 4.19.0 (with PR opentui: fatal: undefined is not an object (evaluating 'color.buffer') #4719 fix already merged)
  • Agent: Sisyphus - Ultraworker
  • Reproducible: yes, on demand (see below)

Reproduction

  1. In OpenCode Web UI, open a session using Sisyphus - Ultraworker agent with default model minimax-cn-coding-plan/MiniMax-M3.
  2. Send a coding investigation task that requires the agent to (a) reason about findings, (b) decide to call a tool, and (c) write a final text report. Example: ask the agent to check a backend file for an arithmetic bug and produce a diagnosis.
  3. Observe the agent's first turn: it may emit one tool_call part correctly, then on the subsequent turn produce an assistant message with only reasoning content and no text or tool parts.
  4. The user prompt that reliably triggers it in our setup is "继续,把结论写出来" (continue, write the conclusion) after the agent has already found the relevant code.
  5. Switch the model dropdown to deepseek-v4-flash and send the same prompt → the same agent emits reasoning + text + tool parts normally and the conversation proceeds.

Symptom (DB evidence)

Database: ~/.local/share/opencode/opencode.db

A failing turn produced this single assistant message (excerpted from message + part tables; role/model/tokens come from message.data, part.data):

msg_id: msg_00eb4ce45001G3qHLlgD3hqm0N
time_created: 2026-08-17 15:52:05
data:
  role: assistant
  agent: Sisyphus - ultraworker
  modelID: MiniMax-M3
  providerID: minimax-cn-coding-plan
  finish: stop
  tokens: { total: 430932, input: 430177, output: 627, reasoning: 0,
            cache: { write: 0, read: 128 } }

Parts in this message:

part[0] type=step-start    text_len=0
part[1] type=reasoning     text_len=1741   ← only reasoning, NO text, NO tool
part[2] type=step-finish   text_len=0

The 1741-char reasoning content describes the model's internal plan — including a fully-formatted bash invocation it intends to make — but finish_reason is already stop, no tool_calls were emitted, and no assistant text reply exists.

For comparison, the same prompt with deepseek-v4-flash immediately afterwards produced three assistant messages, all of which had [reasoning, text, tool] parts and finish: tool-calls/stop properly sequenced:

[15:57:38] assistant  finish=tool-calls  out=399  types=[reasoning, text, tool]
[15:58:32] assistant  finish=tool-calls  out=613  types=[reasoning, text, tool]
[15:58:59] assistant  finish=stop        out=932  types=[text]

This is the exact same bug signature described in #42677 ("opencode run silently saves an empty assistant stop message (tokens counted, zero parts) and exits 0"). We are seeing it not with ollama but with MiniMax's hosted OpenAI-compatible endpoint, and not on opencode run but inside an interactive Tauri/WebUI session.

Evidence: MiniMax-M3 SSE stream format does not use delta.reasoning_content

Captured by hitting https://api.minimaxi.com/v1/chat/completions directly with a Bearer key from ~/.local/share/opencode/auth.json, model=MiniMax-M3, stream=true, prompt="计算 17*23 等于多少?只输出数字":

HTTP 200
Content-Type: text/event-stream; charset=utf-8

chunk [0]:  delta = { role: "assistant" }
chunk [2]:  delta.content = "The user is asking me to calculate 17 * 23 and only output the number.\n\n"
chunk [4]:  delta.content = "17 * 23 = 17 * 20 + 17 * 3 = "
chunk [6]:  delta.content = "340 + 51 = 391\n\nThe user wants only"
chunk [8]:  delta.content = "the number as output."
chunk [10]: delta.content = "\n\n391"                                  ← thinking closes, then the answer
chunk [12]: delta = { role: "assistant" }, finish_reason: "stop"

DeepSeek's stream under the same test uses the OpenAI-standard delta.reasoning_content field separately from delta.content (visible in chunked deltas when reasoning_effort is set).

MiniMax-M3 embeds its reasoning inside delta.content wrapped in <think>...</think> tags, with no reasoning_content field anywhere. When the same chunk stream also contains a planned tool_call, both end up serialized into the same delta.content blob with custom ]<minim>...<tool_call> pseudo-tags, which OpenCode 1.18.18 does not appear to parse as separate parts.

Hypothesized root cause

OpenCode's @ai-sdk/openai-compatible provider (or its downstream message materializer) assumes reasoning content arrives in delta.reasoning_content (the OpenAI standard since o1). For MiniMax-M3 the reasoning text arrives inside delta.content together with the answer and any pseudo-tool-call XML, so:

  1. The provider sees only one big content string and never constructs a dedicated reasoning part — but does happen to attach a reasoning part (1741 chars in our example), suggesting some heuristic extraction may be running partially.
  2. The same content blob is not recognized as valid text because it contains … markers and pseudo-tool-call tags, so OpenCode declines to materialize a text part.
  3. No native tool_calls are in the stream because MiniMax's pseudo-tool-call syntax is non-standard, so no tool part is created either.
  4. The stream ends with finish_reason: "stop" and OpenCode writes the message with only the (half-recognized) reasoning part — which is exactly what we see.

Net effect from the user: the agent "thinks" for one round, then the UI shows nothing and the session stalls.

Why this is hard to detect automatically

  • The response is HTTP 200, complete, and tokens are counted (out=627 in our run).
  • No error is raised; OpenCode simply believes the turn is finished.
  • It only manifests with reasoning-capable models that do not use the delta.reasoning_content convention. DeepSeek and Kimi both use it and are unaffected.

Suggested investigation

Workarounds (none are upstream fixes)

Until this is fixed, our options are:

  1. Switch the agent's primary model to a provider that emits delta.reasoning_content (e.g. deepseek/deepseek-v4-flash, kimi-for-coding/k2p7). Confirmed working in the same session.
  2. Write a thin local proxy that re-emits MiniMax-M3's SSE stream with reasoning split into delta.reasoning_content and answers into delta.content, then point OpenCode's baseURL at the proxy. (We can provide a minimal Python implementation if useful.)
  3. Disable oh-my-openagent — does not fix this bug (we verified the same DB signature with and without OMO, because the issue is in the OpenAI-compatible provider layer, not the plugin).

Related issues

Possibly also related to the oh-my-openagent model-capability work: PR #4719 (snapshot > heuristic for supportsThinking), PR #4716 (k2p* thinking fix), PR #3666 (mark minimax and non-thinking kimi as supportsThinking:false). Those are upstream-side fixes for a different symptom, but they confirm MiniMax-M3 is currently classified as thinking-capable in OMO 4.19+ bundled snapshots, so the request side is sending reasoning-capable requests and the model is returning reasoning — OpenCode just fails to materialize it.

Activity

  1. github-actions commented on Aug 17, 2026

    @github-actions
    Contributor

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

  2. mvallebr commented on Sep 4, 2026

    @mvallebr

    This looks closely related to a workaround I documented here:

    https://gist.github.com/mvallebr/877f2dabb5da269091d2e2725a77b218

    MiniMax-M3’s OpenAI-compatible endpoint can return reasoning embedded in delta.content unless reasoning_split: true is included in the request. With that option enabled, the API returns separate reasoning fields and clean answer content. This may prevent OpenCode from saving a turn containing only a reasoning part and no text or tool part.

    The configuration also explicitly sets thinking.type to adaptive. If this is confirmed, perhaps the same handling could eventually be incorporated into OpenCode’s default MiniMax provider, so users would not need a custom provider configuration.

  3. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

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