Skip to content

deepseek reasoning: final answer merges into the thinking block under /reasoning on (no discrete thinking_end at the reasoning→content transition) #95280

Description

@Marvinthebored

Intended behavior

Under /reasoning on, a reasoning model's thinking should be sealed as its own block, and the final answer delivered as a separate message/block.

Actual behavior

For deepseek-v4 (api openai-completions, reasoning: true), the final answer is appended into the last 🧠 reasoning block rather than emitted as a distinct answer — the thought and the answer run together. This is independent of the delivery fixes (#95029 Discord, #95051 Telegram): once the reasoning lane delivers, the content itself is merged.

Root cause (source-level)

deepseek streams reasoning via reasoning_content deltas, then switches to the answer via content deltas, with no boundary event between them. In src/llm/providers/openai-completions.ts:

  • reasoning_contentthinking_delta (opens the thought)
  • contenttext_delta (the answer)
  • thinking_end is emitted only by finishBlock, which runs once at end-of-stream (the for (const block of blocks) finishBlock(block) loop, ~L462–464) — never at the reasoning→content transition.

So the event order the subscribe handler sees is:

thinking_delta … text_delta … text_delta … [stream end] thinking_end

Because onReasoningEnd is keyed on thinking_end (src/agents/embedded-agent-subscribe.handlers.messages.ts, ~L633), it fires only after the answer has already streamed — too late. The channel keeps the reasoning block open across the answer text and merges them.

Providers that DO emit a discrete thinking_end at the transition (Anthropic, OpenAI-responses, Google, Mistral) are unaffected.

Reproduction

  • Model: deepseek/deepseek-v4-flash (api openai-completions, reasoning enabled)
  • /reasoning on, /think high
  • Prompt anything that produces visible reasoning followed by an answer
  • Observe: the answer text appears inside the last thinking block

Deterministic (unit-level): a handler test that feeds thinking_delta then text_delta with no thinking_end and asserts the reasoning stream closes before the answer fails on current code (onReasoningEnd called 0 times), because nothing closes the thought at the text-lane transition.

Proposed fix

Treat the text-lane opening as the reasoning boundary for native-thinking providers: when a text delta begins while the reasoning stream is still open and we are not inside a tag-based <think> block (whose end is detected during content parsing), close the reasoning stream so the thought is sealed before the answer. PR to follow.

Environment

Reported with AI assistance (Claude); behavior verified on a live OpenClaw 2026.6.9-beta.1 instance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions