openclaw - ✅(Solved) Fix [Bug]: OpenRouter/Qwen3 stream parsing fails — reasoning_details field not handled, causes payloads=0 [3 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#66833Fetched 2026-04-15 06:24:11
View on GitHub
Comments
1
Participants
2
Timeline
12
Reactions
0
Author
Timeline (top)
referenced ×5cross-referenced ×3labeled ×2closed ×1

When using openrouter/qwen/qwen3-235b-a22b (or any Qwen3 model via OpenRouter), every response fails with:

incomplete turn detected: runId=... stopReason=stop payloads=0 — surfacing error to user

The agent shows "⚠️ Agent couldn't generate a response. Please try again."

Error Message

incomplete turn detected: runId=... stopReason=stop payloads=0 — surfacing error to user

Root Cause

Root Cause (community-diagnosed in Discord)

Fix Action

Fix / Workaround

Workaround

PR fix notes

PR #66842: fix(agents): handle OpenRouter reasoning_details in stream parser

Description (problem / solution / changelog)

Summary

  • Problem: OpenRouter-served Qwen3 streams emit thinking/reasoning inside a reasoning_details array (entries like { type: "reasoning.text", text, index }), but the OpenAI-completions stream parser only reads reasoning out of the top-level string fields reasoning_content / reasoning / reasoning_text.
  • Why it matters: because no content or reasoning block is assembled from reasoning_details, the assistant turn completes with payloads=0 and the user sees ⚠️ Agent couldn't generate a response. Please try again. — even though the provider returned a valid response.
  • What changed: after the existing string reasoning-field branch in processOpenAICompletionsStream, pick up choice.delta.reasoning_details when it is an array, concatenate each entry's text field, and stream it into a thinking block the same way as the existing reasoning fields. Two regression tests lock the new branch in: one that assembles multi-chunk reasoning_details deltas plus a text delta into thinking + text blocks, and one that confirms empty reasoning_details entries do not open a spurious thinking block when only a content delta follows.
  • What did NOT change (scope boundary): this PR does not touch OpenRouter request wrapping (createOpenRouterWrapper), request-side reasoning/exclude behavior, the OpenAI Responses transport, any non-completions stream, or the thinking-block schema / sanitization logic.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #66833
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: processOpenAICompletionsStream only checks reasoning_content / reasoning / reasoning_text on choice.delta. OpenRouter's Qwen3 responses (and other OpenRouter models that use reasoning_details) put the reasoning text inside an array of entries keyed by type: "reasoning.text" with a text field, which the parser never reads. With no text content delta and no recognized reasoning field, output.content stays empty and the runtime surfaces payloads=0.
  • Missing detection / guardrail: no coverage exercised the OpenRouter reasoning_details shape on the OpenAI-completions stream.
  • Contributing context (if known): OpenRouter passes Qwen3's native response through verbatim; the reporter confirmed (issue #66833) that a direct OpenRouter call returns content + reasoning_details, but the OpenClaw gateway path consistently hits payloads=0.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/openai-transport-stream.test.ts (two new tests that exercise __testing.processOpenAICompletionsStream).
  • Scenario the test should lock in: (1) a stream that sends two reasoning_details entries across chunks followed by a content delta assembles into one thinking block (Thinking about it.) plus one text block (Final answer.) and emits thinking_start + two thinking_delta events; (2) a stream whose only reasoning_details entry has empty text does NOT open a thinking block when the following chunk only carries a content delta.
  • Why this is the smallest reliable guardrail: the failure is in the stream-chunk reducer itself, so direct tests against processOpenAICompletionsStream are enough — no transport/network harness needed.
  • Existing test that already covers this (if any): none — existing reasoning tests exercise the string-shaped reasoning_content / reasoning / reasoning_text branch only.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

OpenRouter-routed Qwen3 sessions (and any OpenAI-completions stream that emits reasoning_details) now complete turns normally instead of failing with ⚠️ Agent couldn't generate a response., and the model's reasoning text is preserved as a thinking block with thinkingSignature: "reasoning_details" (consistent with how the existing reasoning fields tag their thinking blocks).

Diagram (if applicable)

Before:
[OpenRouter Qwen3 stream]
  delta: { reasoning_details: [{ type: "reasoning.text", text: "..." }] }
  delta: { content: "..." }  // optional
-> reducer only inspects reasoning_content / reasoning / reasoning_text
-> no thinking block, no text block from reasoning_details
-> output.content = [] -> payloads=0 -> "Agent couldn't generate a response"

After:
[OpenRouter Qwen3 stream]
  delta: { reasoning_details: [{ type: "reasoning.text", text: "..." }] }
  delta: { content: "..." }
-> reducer also reads reasoning_details[].text, concatenates, and emits a thinking block
-> text deltas still build a text block as before
-> output.content = [ thinking, text ] -> turn completes normally

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS (local validation); issue reported on macOS arm64 and reproduces on any host running OpenClaw 2026.4.14 against an OpenRouter Qwen3 model.
  • Runtime/container: Node.js 22 / pnpm workspace.
  • Model/provider: openrouter/qwen/qwen3-235b-a22b (and other Qwen3 OpenRouter variants).
  • Integration/channel (if any): agent message path going through the OpenAI-completions stream transport.
  • Relevant config (redacted): model: "openrouter/qwen/qwen3-235b-a22b" on a standard agent; no special reasoning overrides required.

Steps

  1. Configure an agent with model: "openrouter/qwen/qwen3-235b-a22b" and send any user message.
  2. Observe on main that the turn completes with incomplete turn detected: ... stopReason=stop payloads=0 and the UI surfaces the "Agent couldn't generate a response" warning.
  3. With this PR applied, the same turn assembles a thinking block from the streamed reasoning_details entries and, if the model also emits content, a normal text block — no payloads=0 failure.

Expected

  • Turns against OpenRouter Qwen3 complete with at least one assistant content block and surface the model's reasoning (and text, when present) instead of failing with payloads=0.

Actual

  • Before this PR: payloads=0, "Agent couldn't generate a response" user-visible error, content dropped silently.
  • After this PR: thinking block captures reasoning_details text; text block captures any content delta; turn completes normally.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: added two direct unit tests against __testing.processOpenAICompletionsStream that (a) feed two-chunk reasoning_details plus a final content delta and assert a single thinking block, a single text block, and the expected thinking/text event emissions, and (b) feed a single empty-text reasoning_details entry plus a content delta and assert that no thinking block is opened. Ran node scripts/test-projects.mjs src/agents/openai-transport-stream.test.ts locally: 54/54 tests pass.
  • Edge cases checked: non-array reasoning_details is ignored; array entries without a string text field contribute nothing; empty concatenated text does not open a thinking block (the reducer continues on to tool_calls / next chunk); thinkingSignature is set to "reasoning_details" so consumers can distinguish it from the existing string-field branches.
  • What you did not verify: I did not run a live OpenRouter Qwen3 request in this session.

Review Conversations

  • I will reply to and resolve every bot review conversation I address in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No

Risks and Mitigations

  • Risk: If another OpenAI-compatible provider sends reasoning_details with a different semantic shape, we would fold its reasoning text into a thinking block rather than drop it.
    • Mitigation: we only read the string text field of each entry and only open a thinking block when the concatenated text is non-empty; the change is strictly additive to the existing string-field branch and does not alter behavior for providers that never emit reasoning_details.
  • Risk: reasoning_details entries whose text is already partial (e.g. cumulative) could be double-counted.
    • Mitigation: the OpenAI/OpenRouter streaming contract is delta-based across chunks, consistent with how reasoning_content is already appended; this PR follows the same append pattern.

AI Assistance

  • AI-assisted: Yes (Claude)
  • Testing degree: Fully tested for the affected stream reducer branch
  • Prompt/session summary: Identified issue #66833 as a freshly reported, source-documented bug with no active competing PR, traced the reducer at src/agents/openai-transport-stream.ts to the existing reasoningFields branch, added a strictly-additive reasoning_details branch that follows the same thinking-block/event-emission pattern, then wrote two direct unit tests against __testing.processOpenAICompletionsStream and confirmed the focused suite passes locally.
  • Understanding confirmation: I understand this change only widens the completions stream reducer's reasoning-field recognition to include OpenRouter's reasoning_details array shape; it does not change request-side OpenRouter wrapping, the Responses transport, or any thinking-block downstream consumer.

Verification Commands

  • node scripts/test-projects.mjs src/agents/openai-transport-stream.test.tsTest Files 1 passed (1) / Tests 54 passed (54)
  • pnpm exec oxlint src/agents/openai-transport-stream.ts src/agents/openai-transport-stream.test.tsFound 0 warnings and 0 errors.
  • pnpm format src/agents/openai-transport-stream.ts src/agents/openai-transport-stream.test.ts → clean
  • pnpm exec tsgo --noEmit → clean
  • git diff --check → clean

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/openai-transport-stream.test.ts (modified, +447/-0)
  • src/agents/openai-transport-stream.ts (modified, +46/-0)

PR #66905: fix(openrouter): handle reasoning_details in Qwen3 stream parsing

Description (problem / solution / changelog)

Summary

Fix OpenRouter/Qwen3 models returning empty responses due to unrecognized reasoning_details field.

Problem: When using openrouter/qwen/qwen3-235b-a22b or any Qwen3 model via OpenRouter, every response fails with "incomplete turn detected: payloads=0" and the user sees "⚠️ Agent couldn't generate a response."

Root Cause: The stream parser in src/agents/openai-transport-stream.ts handled reasoning_content, reasoning, and reasoning_text but not reasoning_details — which Qwen3 returns via OpenRouter. The unhandled field caused zero content blocks to be assembled, triggering the incomplete turn path.

Solution: Add handling for reasoning_details array. Extract text from items with type: "reasoning.text" and treat as thinking content, enabling successful turn completion.

Additional defensive fixes:

  • Add ?? "" guards in src/channels/plugins/setup-wizard.ts:458 and src/channels/plugins/setup-wizard-helpers.ts:994 to prevent potential undefined trim errors from clack wrapper edge cases (belt-and-suspenders approach mentioned in issue comments).

Changes

  • openai-transport-stream.ts: Add reasoning_details handling in processOpenAICompletionsStream, extracting reasoning text from array items and creating thinking blocks.
  • openai-transport-stream.test.ts: Add test case verifying correct handling of Qwen3 reasoning_details.
  • setup-wizard.ts & setup-wizard-helpers.ts: Add defensive guards around prompter.text() results.

Test plan

  • New unit test: handles reasoning_details from OpenRouter/Qwen3 in completions stream passes
  • All 53 existing openai-transport-stream.test.ts tests pass
  • Fix verified by community members in original issue thread

AI Code Generation Disclosure

  • 🤖 This PR was created with AI assistance (Claude Codex)
  • The fix implementation and test were generated based on issue description and community diagnosis

Environment

  • OpenClaw version: 2026.4.14 (base)
  • Tested with: Bun test runner, TypeScript type-checking
  • Provider: OpenRouter
  • Model: openrouter/qwen/qwen3-235b-a22b, qwen3-32b

Checklist

  • Build successful (no type errors)
  • Tests added/updated and passing
  • Issue reference added (Fixes #66833)
  • Commit message follows conventional format
  • PR description detailed with test plan
  • Defensive coding practices applied

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/openai-transport-stream.test.ts (modified, +290/-0)
  • src/agents/openai-transport-stream.ts (modified, +67/-19)

PR #66929: fix(agents): handle Qwen3 reasoning_details in OpenRouter stream

Description (problem / solution / changelog)

Summary

Qwen3 models via OpenRouter return reasoning content in the reasoning_details field on streamed choice.delta. The existing OpenAI transport stream parser only recognized reasoning_content, reasoning, and reasoning_text, causing turns to end with payloads=0 and surface an incomplete-turn error to users.

Change Type

  • Bug fix

Related Issue

Fixes #66833

Testing

  • pnpm vitest run src/agents/openai-transport-stream.test.ts — 52/52 passed
  • pnpm tsgo — passed
  • pnpm lint — 0 warnings, 0 errors

Checklist

  • Minimal, focused change
  • Existing tests pass
  • Type-check passes
  • Lint passes

Changed files

  • src/agents/openai-transport-stream.ts (modified, +6/-1)

Code Example

"reasoning_details": [
  {
    "type": "reasoning.text",
    "text": "...",
    "format": "unknown",
    "index": 0
  }
]
This field is not handled in the stream parser, causing zero content blocks to be assembled.

Expected Fix

Handle choice.delta.reasoning_details in openai-transport-stream.ts the same way reasoning_content is handled — extract text content and treat as reasoning tokens, not as a blocking field.

Alternatively, on the OpenRouter response path, strip or ignore unknown reasoning fields so payloads=0 is not triggered when the model returns only reasoning with no text delta.

Workaround

None available in 2026.4.14 without a litellm proxy to strip reasoning_details before OpenClaw parses the stream.


### Steps to reproduce

## Steps to Reproduce

1. Create an agent with `model: "openrouter/qwen/qwen3-235b-a22b"`
2. Send any message to the agent
3. Observe `incomplete turn detected: payloads=0` in gateway logs
4. User receives "⚠️ Agent couldn't generate a response"


### Expected behavior

When a model returns `reasoning_details` in the stream, OpenClaw should treat it the same as other recognised reasoning fields (`reasoning_content`, `reasoning`, `reasoning_text`) — extract the text content and continue assembling the response. The assistant turn should complete successfully with the model's reply visible to the user.


### Actual behavior

When a Qwen3 model responds via OpenRouter, the stream includes a `reasoning_details` field that OpenClaw does not recognise. Because no content blocks are assembled from the unhandled field, the turn completes with `payloads=0`. The agent fails silently and the user sees "⚠️ Agent couldn't generate a response. Please try again."


### OpenClaw version

OpenClaw: 2026.4.14 (323493f)

### Operating system

OS: macOS arm64 (Darwin 25.3.0)

### Install method

npm (stable)

### Model

Model: openrouter/qwen/qwen3-235b-a22b

### Provider / routing chain

Provider: OpenRouter

### Additional provider/model setup details

_No response_

### Logs, screenshots, and evidence
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Summary

When using openrouter/qwen/qwen3-235b-a22b (or any Qwen3 model via OpenRouter), every response fails with:

incomplete turn detected: runId=... stopReason=stop payloads=0 — surfacing error to user

The agent shows "⚠️ Agent couldn't generate a response. Please try again."

Root Cause (community-diagnosed in Discord)

src/agents/openai-transport-stream.ts handles these reasoning fields on streamed choice.delta:

  • reasoning_content
  • reasoning
  • reasoning_text

But not reasoning_details — which Qwen3 returns via OpenRouter.

The OpenRouter hook path (createOpenRouterWrapper) is request-side wrapping only — no response normalisation. So reasoning_details passes through unhandled, leaving OpenClaw with no assistant payload blocks, which hits the payloads=0 incomplete-turn path in src/agents/pi-embedded-runner/run.ts.

What was tried (none worked)

  • thinkingDefault: "off" — only stops injecting reasoning effort, does not send OpenRouter exclude: true
  • tools.allow: [] — no tools
  • providerOptions.openrouter.extra_body.enable_thinking: false
  • providerOptions.openrouter.extra_body.thinking: { type: "disabled" }
  • Model variants: qwen3-235b-a22b, qwen3-235b-a22b-2507, qwen3-32b

Direct OpenRouter API call with the same model works correctly — returns content + reasoning_details.

Verification

Raw OpenRouter response includes:

"reasoning_details": [
  {
    "type": "reasoning.text",
    "text": "...",
    "format": "unknown",
    "index": 0
  }
]
This field is not handled in the stream parser, causing zero content blocks to be assembled.

Expected Fix

Handle choice.delta.reasoning_details in openai-transport-stream.ts the same way reasoning_content is handled — extract text content and treat as reasoning tokens, not as a blocking field.

Alternatively, on the OpenRouter response path, strip or ignore unknown reasoning fields so payloads=0 is not triggered when the model returns only reasoning with no text delta.

Workaround

None available in 2026.4.14 without a litellm proxy to strip reasoning_details before OpenClaw parses the stream.


### Steps to reproduce

## Steps to Reproduce

1. Create an agent with `model: "openrouter/qwen/qwen3-235b-a22b"`
2. Send any message to the agent
3. Observe `incomplete turn detected: payloads=0` in gateway logs
4. User receives "⚠️ Agent couldn't generate a response"


### Expected behavior

When a model returns `reasoning_details` in the stream, OpenClaw should treat it the same as other recognised reasoning fields (`reasoning_content`, `reasoning`, `reasoning_text`) — extract the text content and continue assembling the response. The assistant turn should complete successfully with the model's reply visible to the user.


### Actual behavior

When a Qwen3 model responds via OpenRouter, the stream includes a `reasoning_details` field that OpenClaw does not recognise. Because no content blocks are assembled from the unhandled field, the turn completes with `payloads=0`. The agent fails silently and the user sees "⚠️ Agent couldn't generate a response. Please try again."


### OpenClaw version

OpenClaw: 2026.4.14 (323493f)

### Operating system

OS: macOS arm64 (Darwin 25.3.0)

### Install method

npm (stable)

### Model

Model: openrouter/qwen/qwen3-235b-a22b

### Provider / routing chain

Provider: OpenRouter

### Additional provider/model setup details

_No response_

### Logs, screenshots, and evidence

```shell

Impact and severity

No response

Additional information

No response

extent analysis

TL;DR

Handle the reasoning_details field in openai-transport-stream.ts to extract text content and treat it as reasoning tokens.

Guidance

  • Modify src/agents/openai-transport-stream.ts to handle reasoning_details similar to reasoning_content, by extracting the text content and treating it as reasoning tokens.
  • Alternatively, consider stripping or ignoring unknown reasoning fields on the OpenRouter response path to prevent payloads=0 from being triggered.
  • Verify the fix by checking if the agent can generate a response successfully when using a Qwen3 model via OpenRouter.
  • Test the modified code with different model variants, such as qwen3-235b-a22b, qwen3-235b-a22b-2507, and qwen3-32b, to ensure the fix works across various models.

Example

// In src/agents/openai-transport-stream.ts
if (choice.delta.reasoning_details) {
  const reasoningText = choice.delta.reasoning_details[0].text;
  // Treat reasoningText as reasoning tokens and continue assembling the response
}

Notes

The provided fix assumes that the reasoning_details field contains text content that can be extracted and treated as reasoning tokens. If the field contains other types of data, additional modifications may be necessary.

Recommendation

Apply the workaround by modifying openai-transport-stream.ts to handle reasoning_details, as this is the most direct way to address the issue in the current version (2026.4.14) without relying on external proxies or upgrades.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

When a model returns reasoning_details in the stream, OpenClaw should treat it the same as other recognised reasoning fields (reasoning_content, reasoning, reasoning_text) — extract the text content and continue assembling the response. The assistant turn should complete successfully with the model's reply visible to the user.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: OpenRouter/Qwen3 stream parsing fails — reasoning_details field not handled, causes payloads=0 [3 pull requests, 1 comments, 2 participants]