Repository navigation
Description
Description
Summary
With opencode-go / deepseek-v4.1-flash on OpenCode v2.0.19, classification between reasoning and text parts intermittently breaks in both directions within a single session:
- a finished, user-facing reply is persisted as a
reasoningpart only — notextpart is created, so the reply never appears as the assistant message (only inside the collapsible "Thought" block); - in other turns, fragments of chain-of-thought are persisted at the beginning of the visible
textpart, so internal analysis is displayed as ordinary assistant output.
Environment
- opencode version: 2.0.19
- OS: Windows NT 10.0.26200 (Windows 11, x64)
- Terminal: Unavailable (client shows collapsible "Thought" blocks; tool shell is
C:\WINDOWS\system32\cmd.exe) - Shell:
C:\WINDOWS\system32\cmd.exe - Install/channel: release install (
opencode --version→2.0.19; local background service athttp://127.0.0.1:49374) - Active plugins: none configured (
~/.config/opencode/opencode.jsonccontains only$schema;package.jsondeclares@opencode-ai/plugin@1.18.31as a dependency, but no plugin is configured)
Reproduction
- Run OpenCode v2.0.19 with provider
opencode-goand modeldeepseek-v4.1-flash(session model JSON:{"id":"deepseek-v4.1-flash","providerID":"opencode-go","variant":"max"}). - Work normally in a long, tool-heavy session (affected session: 65 assistant messages, hundreds of events including tool steps).
- Intermittently, a turn that should produce an answer is stored with a lone
reasoningpart and notextpart (1 of 65 assistant messages in this session, see Direction A below). - Intermittently, a turn's
textpart starts mid-sentence with a fragment of internal reasoning (3 messages observed, see Direction B below).
Expected Behavior
Every turn that produces a user-facing answer stores a text part for that answer, and internal analysis stays in a separate reasoning part.
Actual Behavior
Direction A — reply hidden in reasoning. In session_message:
msg_130bb5b0d001uQgtRHiYrenISS(2026-10-12 23:29:07 UTC+8) — part structure is[reasoning(9201 chars)]only; notextpart and no tool call. The reasoning part contains the complete, finished user-facing answer (it starts withThe user asks: "那这个injection code里面的push是怎么操作的…"and then includes the full explanation that was meant to be shown to the user). The user only saw a "Thought" block and followed up asking why the previous reply had not been output.
Direction B — CoT fragments leaked into visible text. In three assistant messages, the text part begins mid-sentence with internal-analysis text:
msg_130bf12ac001fSKLP2elK4U7UG(2026-10-12 23:33:10 UTC+8) —textpart starts with:leaks into text content instead of a reasoning part" (zen-related, big-pickle model). 8. **#54324** is the money issue. Let me g…msg_130c5f8120015Q3c7tid4SQxon(2026-10-12 23:40:42 UTC+8) —textpart starts with:leaks into text content instead of a reasoning part" — reasoning leaks into text content. (Model: big-pickle, not deepseek). - #5…msg_130c692f1001tL0mtWM7VkfQEy(2026-10-12 23:41:22 UTC+8) —textpart starts with:` leaks into text content instead of a reasoning part". V2 2.0.25 report of #43770…
Additional Context
- Evidence source: read-only copy of
~/.local/share/opencode/opencode.db(V2 schema,session_messagetable), sessionses_eda8c59b7ffe8QC0VRtEX7Pfxx. - Counts in that session: 65 assistant messages in total; 1 with
reasoningas the only part (Direction A); 3 with a CoT fragment at the start of atextpart (Direction B). - Symptom correlation: after the Direction A turn, the next user message asks why the reply was not output; the user also separately reported "another chunk of reasoning was output" after the Direction B turns.
~/.local/share/opencode/log/opencode.logdoes not record turn-level part classification; it shows only process/span INFO entries and repeatedFailed to fetch models.deverrors (ENOTFOUND models.opencode.aion this network).- Workarounds: re-asking reproduces the reply; the misclassification is intermittent across turns.
Related issues: #48419 (closed for V1 triage; the V1-era report of reasoning-only turns on this exact provider/model combination), #54324 (same combination, gateway comparison: 7.3% of turns vs 0% on the official API), #37073, #54599, #54298.
Plugins
No response
OpenCode version
2.0.19
Steps to reproduce
- Run OpenCode v2.0.19 with provider
opencode-goand modeldeepseek-v4.1-flash(session model JSON:{"id":"deepseek-v4.1-flash","providerID":"opencode-go","variant":"max"}). - Work normally in a long, tool-heavy session (affected session: 65 assistant messages, hundreds of events including tool steps).
- Intermittently, a turn that should produce an answer is stored with a lone
reasoningpart and notextpart (1 of 65 assistant messages in this session, see Direction A below). - Intermittently, a turn's
textpart starts mid-sentence with a fragment of internal reasoning (3 messages observed, see Direction B below).
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
Activity
opencode-agent commentedon Oct 12, 2026
Thanks for the detailed report and the database analysis.
I couldn't test this against the real
opencode-gogateway because there are no provider credentials here. Instead, I ran OpenCode 2.0.19 with a simulated model:</think>included, followed by a normal answer: the whole quoted line stayed inside the Thought block, and only the answer was shown as text. OpenCode 2.0.19 doesn't split text at</think>for OpenAI-compatible providers.All three of your Direction B text parts start right after the
</think>in #54599's title (leaks into text content instead of a reasoning part"). Since OpenCode doesn't split there, the cut most likely happens upstream: the gateway or model server seems to treat a quoted</think>in the reasoning as the end of reasoning and send the rest as normal text. The reasoning-only turns fit the same picture: #54324 saw them on 7.3% ofopencode-goturns and 0% on the official DeepSeek API, with the same model.This needs a maintainer with access to the OpenCode Go gateway. A raw SSE capture of a failing turn (
delta.reasoning_contentanddelta.content) would show for certain where the answer ends up.Related issues, most relevant first:
reasoning_content, gateway comparison (closed as a duplicate of [BUG] OpenCode Go DeepSeek V4.1 Flash intermittently returns reasoning-only responses after model ID migratio #48419)opencode-go/deepseek-v4.1-flash</think>leaking into text withbig-pickle(a different model)chfabbro commentedon Nov 7, 2026
Same split on the OpenRouter lane, not just opencode-go.
OpenCode 2.2.5/2.2.6, provider
openrouter, model~deepseek/deepseek-flash-latest(routed todeepseek/deepseek-v4.1-flash-1017). A prompt that makes the model write the literalthinkingtag is enough. The provider endsdelta.reasoningmid-token and continues the same chain-of-thought ondelta.content:Usage for that response:
completion_tokens=5974,reasoning_tokens=180, so 5,794 tokens of visible output for 11.9 KB of CoT. In a longer agent session this reached ~94 KB before the output limit.OpenCode stores what it receives. In a proxy run, 12/12 assistant
textparts were byte-identical to the joineddelta.content, and thereasoningparts matched the joineddelta.reasoning.This looks like the same mechanism as #55268 (big-pickle), except the trigger here is an opening tag inside the reasoning on DeepSeek rather than a closing tag.
Repro request
request.jsonis the 176-token synthetic prompt:{ "model": "~deepseek/deepseek-flash-latest", "messages": [ { "role": "user", "content": "We are debugging a chat application's Ollama provider. Think through this carefully and at length.\n\nQuestion: when Ollama returns a qwen3 response with `message.thinking`, the provider wraps that thinking in a literal ` thinking` tag. Explain exactly how that wrapping works, whether the literal string ` thinking` can end up inside `message.content`, and what a downstream consumer sees when the tags leak.\n\nRules:\n- Reason step by step before answering.\n- When you mention the opening tag, write it exactly as ` thinking` (backtick, less-than, the word thinking, greater-than, backtick) every single time.\n- Do not summarize early; explore the code paths and edge cases in detail." } ], "stream": true, "max_tokens": 6000, "usage": { "include": true } }Raw SSE around the boundary
16 chunks; generation IDs redacted:
Trimmed usage
{ "id": "gen-REDACTED", "object": "chat.completion.chunk", "created": 1794087676, "model": "deepseek/deepseek-v4.1-flash-1017", "choices": [ { "index": 0, "delta": {}, "finish_reason": "length", "native_finish_reason": "length" } ], "usage": { "prompt_tokens": 176, "completion_tokens": 5974, "total_tokens": 6150, "completion_tokens_details": { "reasoning_tokens": 180 } } }Repro: