Skip to content

reasoning: v2.0.19 + opencode-go/deepseek-v4.1-flash — replies hidden inside reasoning, and CoT fragments leaked into text #54861

Open

Description

@dazi9

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 reasoning part only — no text part 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 text part, 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 at http://127.0.0.1:49374)
  • Active plugins: none configured (~/.config/opencode/opencode.jsonc contains only $schema; package.json declares @opencode-ai/plugin@1.18.31 as a dependency, but no plugin is configured)

Reproduction

  1. Run OpenCode v2.0.19 with provider opencode-go and model deepseek-v4.1-flash (session model JSON: {"id":"deepseek-v4.1-flash","providerID":"opencode-go","variant":"max"}).
  2. Work normally in a long, tool-heavy session (affected session: 65 assistant messages, hundreds of events including tool steps).
  3. Intermittently, a turn that should produce an answer is stored with a lone reasoning part and no text part (1 of 65 assistant messages in this session, see Direction A below).
  4. Intermittently, a turn's text part 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; no text part and no tool call. The reasoning part contains the complete, finished user-facing answer (it starts with The 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) — text part 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) — text part 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) — text part 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_message table), session ses_eda8c59b7ffe8QC0VRtEX7Pfxx.
  • Counts in that session: 65 assistant messages in total; 1 with reasoning as the only part (Direction A); 3 with a CoT fragment at the start of a text part (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.log does not record turn-level part classification; it shows only process/span INFO entries and repeated Failed to fetch models.dev errors (ENOTFOUND models.opencode.ai on 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

  1. Run OpenCode v2.0.19 with provider opencode-go and model deepseek-v4.1-flash (session model JSON: {"id":"deepseek-v4.1-flash","providerID":"opencode-go","variant":"max"}).
  2. Work normally in a long, tool-heavy session (affected session: 65 assistant messages, hundreds of events including tool steps).
  3. Intermittently, a turn that should produce an answer is stored with a lone reasoning part and no text part (1 of 65 assistant messages in this session, see Direction A below).
  4. Intermittently, a turn's text part 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

  1. opencode-agent commented on Oct 12, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report and the database analysis.

    I couldn't test this against the real opencode-go gateway because there are no provider credentials here. Instead, I ran OpenCode 2.0.19 with a simulated model:

    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% of opencode-go turns 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_content and delta.content) would show for certain where the answer ends up.

    Related issues, most relevant first:

  2. chfabbro commented on Nov 7, 2026

    @chfabbro

    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 to deepseek/deepseek-v4.1-flash-1017). A prompt that makes the model write the literal thinking tag is enough. The provider ends delta.reasoning mid-token and continues the same chain-of-thought on delta.content:

    delta.reasoning: "... clients normalize thinking by wrapping in ` thinking..."
    delta.content:   "` tags. The question likely references a specific provider implementation..."
    

    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 text parts were byte-identical to the joined delta.content, and the reasoning parts matched the joined delta.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.json is 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:

    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"3 models","reasoning_details":[{"type":"reasoning.text","text":"3 models","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" use","reasoning_details":[{"type":"reasoning.text","text":" use","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" thinking. Some","reasoning_details":[{"type":"reasoning.text","text":" thinking. Some","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" clients","reasoning_details":[{"type":"reasoning.text","text":" clients","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" normalize","reasoning_details":[{"type":"reasoning.text","text":" normalize","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" thinking by","reasoning_details":[{"type":"reasoning.text","text":" thinking by","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" wrapping in ` thinking","reasoning_details":[{"type":"reasoning.text","text":" wrapping in ` thinking","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"...","reasoning_details":[{"type":"reasoning.text","text":"...","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"` tags. The","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" question likely references","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" a specific provider","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" implementation.","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" We need explain","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" exactly how wrapping","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" works","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: {"id":"gen-REDACTED","object":"chat.completion.chunk","created":1794087676,"model":"deepseek/deepseek-v4.1-flash-1017","provider":"Alibaba","choices":[{"index":0,"delta":{"content":",","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
    
    data: [DONE]
    

    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:

    curl -sS -N https://openrouter.ai/api/v1/chat/completions \
      -H "Authorization: Bearer $OPENROUTER_API_KEY" \
      -H "Content-Type: application/json" \
      --data-binary @request.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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