openclaw - ✅(Solved) Fix [Bug]: native OpenRouter model openrouter/openrouter/free returns incomplete turn payloads=0 [1 pull requests, 3 comments, 3 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#68185Fetched 2026-04-18 05:53:35
View on GitHub
Comments
3
Participants
3
Timeline
6
Reactions
0
Author
Timeline (top)
commented ×3cross-referenced ×2labeled ×1

Native OpenRouter integration fails for openrouter/openrouter/free with incomplete turn / payloads=0, even in a clean OpenClaw 2026.4.15 container.

Error Message

15:26:54 [agent/embedded] incomplete turn detected: runId=47c21d0f-283b-48c7-bd2f-ddd0b3fdb936 sessionId=47c21d0f-283b-48c7-bd2f-ddd0b3fdb936 stopReason=stop payloads=0 — surfacing error to user

Root Cause

Native OpenRouter integration fails for openrouter/openrouter/free with incomplete turn / payloads=0, even in a clean OpenClaw 2026.4.15 container.

Fix Action

Fixed

PR fix notes

PR #68351: fix(openrouter): surface reasoning_details visible-text as user output (#68261)

Description (problem / solution / changelog)

Summary

  • Problem: OpenRouter model runs (e.g. openrouter/google/gemini-2.5-flash, Grok 4.x, minimax-m2.7) return blank responses with stopReason=stop payloads=0 and surface ⚠️ Agent couldn't generate a response.
  • Why it matters: Entire classes of OpenRouter-proxied models have been unusable since 2026.4.14/15 despite healthy gateway and successful upstream responses.
  • What changed: getCompletionsReasoningDelta in src/agents/openai-transport-stream.ts now discriminates the OpenRouter reasoning_details stream between hidden reasoning (reasoning.text) and OpenAI Responses–style visible output (response.output_text, response.text, text). Visible-output items are routed through the same text-block path as choice.delta.content instead of being buried in a thinking block.
  • What did NOT change (scope boundary): no changes to prompts, auth, sandbox, tool policy, secret handling, provider routing, request assembly, transcript bytes, or prompt-cache order. Previous reasoning.text behavior is preserved byte-for-byte.

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 #68261
  • Related #67410, #67575, #67698, #67907, #67425, #68030, #68076, #68185
  • This PR fixes a bug or regression

Root Cause

e0bf756b50 ("fix: handle OpenRouter Qwen3 reasoning_details streams") taught getCompletionsReasoningDelta to parse reasoning_details items, but it unconditionally classified every matching item as hidden thinking. Some OpenRouter upstreams (Gemini 2.5 Flash via the Responses-style reasoning proxy, xAI Grok 4.x, and related models) deliver the assistant's final answer inside reasoning_details using OpenAI Responses content types such as response.output_text, response.text, and plain text, with an empty choice.delta.content. Because the parser treated those as thinking, the stream closed with stopReason=stop, zero text blocks, and the incomplete-turn detector in src/agents/pi-embedded-runner/run/incomplete-turn.ts surfaced the generic error.

  • Root cause: reasoning_details visible-output types were misclassified as internal reasoning and never emitted as user-visible text.
  • Missing detection / guardrail: no regression covered reasoning_details streams where the final answer arrives without a separate content field.
  • Contributing context: OpenRouter's proxy layer for several reasoning models started folding visible output into reasoning_details alongside reasoning bytes.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/agents/openai-transport-stream.test.ts
  • Scenario the test should lock in: a mixed reasoning_details stream where reasoning.text items stay as thinking while response.output_text, response.text, and text items surface as a user-visible text block with stopReason=stop.
  • Why this is the smallest reliable guardrail: it exercises the exact parser path that decides whether bytes become visible text or hidden thinking.
  • Existing test that already covers this: handles reasoning_details from OpenRouter/Qwen3 in completions stream only covered content + reasoning.text mixes, not the visible-text-in-reasoning_details case.

User-visible / Behavior Changes

  • OpenRouter models that deliver the final answer inside reasoning_details (e.g. Gemini 2.5 Flash, Grok 4.x) now produce visible replies instead of the generic ⚠️ Agent couldn't generate a response error. No configuration, defaults, or docs change.

Diagram

Before:
OpenRouter -> reasoning_details[{type:"response.output_text", text:"hello"}]
           -> classified as thinking
           -> stopReason=stop, payloads=0
           -> "⚠️ Agent couldn't generate a response."

After:
OpenRouter -> reasoning_details[{type:"response.output_text", text:"hello"}]
           -> classified as visible text
           -> text block "hello", stopReason=stop, payloads>=1
           -> user sees "hello"

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Runtime-enforced controls that are explicitly unchanged by this PR: tool policy/allowlist, sandbox, exec approval, subagent policy, secrets plan, owner-only gating, OpenRouter routing, auth profiles, and prompt-cache request assembly. The fix is confined to classification of bytes the upstream already returned over an authenticated, already-policy-gated channel; it does not rely on or modify any prompt text or runtime policy. A malicious upstream that labels reasoning as type: "text" cannot leak more than it could by setting choice.delta.content directly, which is already the default visible path.

Repro + Verification

Environment

  • OS: macOS (Darwin 25.3.0)
  • Runtime/container: local OpenClaw gateway (loopback)
  • Model/provider: openrouter/google/gemini-2.5-flash, openrouter/x-ai/grok-4.1-fast, openrouter/minimax/minimax-m2.7
  • Integration/channel: none (direct openclaw infer model run) and dashboard
  • Relevant config: agents.defaults.models = { "openrouter/google/gemini-2.5-flash": {} } (redacted)

Steps

  1. openclaw infer model run --model openrouter/google/gemini-2.5-flash --prompt "Reply with exactly: hello" --json
  2. Observe response contents and gateway log for incomplete turn detected ... payloads=0.
  3. Repeat for Grok 4.x / minimax-m2.7.

Expected

  • outputs[0].text contains the assistant reply.
  • No payloads=0 / incomplete turn detected warning.

Actual (before fix)

  • outputs[0].text = ⚠️ Agent couldn't generate a response. Please try again.
  • Gateway log: incomplete turn detected: ... stopReason=stop payloads=0 — surfacing error to user.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets

Before: reasoning_details visible-text items classified as thinking, zero text blocks, stopReason=stop payloads=0. After: new test surfaces reasoning_details visible-text as text while keeping reasoning.text as thinking asserts text block emission alongside the preserved thinking block; existing reasoning_details/Qwen3 and tool-call regression tests remain green.

Human Verification (required)

  • Verified scenarios:
    • pnpm test src/agents/openai-transport-stream.test.ts — 56/56 pass including the new case.
    • pnpm test src/agents/openai-transport-stream.test.ts -t reasoning_details — 4/4 reasoning_details cases pass.
    • Re-ran pnpm check (host-env-policy, tool-display, no-conflict-markers green); remaining pnpm tsgo/pnpm lint failures are pre-existing in extensions/qa-lab/src/providers/aimock/server.ts on upstream/main and unrelated to this change (verified by running pnpm tsgo on clean upstream/main).
  • Edge cases checked:
    • reasoning.text-only stream still becomes a thinking block (existing Qwen3 test).
    • Mixed reasoning.text + visible-text items in the same stream produce both thinking and text blocks in order.
    • reasoning_details arriving alongside tool_calls still routes through the buffered thinking path and preserves tool-call arguments (existing tests).
    • Non-array or unknown-type reasoning_details items are skipped silently.
  • What I did not verify:
    • Live OpenRouter run against real keys (no live lane invoked in this change).
    • Parallels smoke / cross-platform onboarding (not in scope for a parser-only fix).

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

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

Risks and Mitigations

  • Risk: a model intentionally using type: "text" inside reasoning_details for genuinely-hidden scratchpad content would now surface that content to users.
    • Mitigation: OpenRouter's documented semantics for response.output_text / response.text / text are visible assistant output; the existing reasoning.text type still maps to hidden reasoning. If a specific upstream deviates, follow-up can narrow the visible-type set.
  • Risk: Qwen3 regression from #66905 returns.
    • Mitigation: the existing Qwen3 regression test and the tool-call + reasoning_details test remain unchanged and still pass.

Testing notes

  • AI-assisted PR, lightly tested (parser unit tests only; no live OpenRouter run).
  • Exact tests run:
    • pnpm test src/agents/openai-transport-stream.test.ts (56/56 pass)
    • pnpm test src/agents/openai-transport-stream.test.ts -t reasoning_details (4/4 pass)
    • pnpm test src/agents/openai-transport-stream.test.ts -t visible-text (1/1 pass)
    • pnpm check (pre-existing aimock/server.ts failures on upstream/main; no new failures introduced)

Made with Cursor

Changed files

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

Code Example

Clean repro command:

docker run --rm -it \
  --env-file /var/docker-skynet/.env \
  ghcr.io/openclaw/openclaw:2026.4.15 \
  sh -lc '
    openclaw models set openrouter/openrouter/free &&
    openclaw infer model run --model openrouter/openrouter/free --prompt "Reply with exactly: smoke-ok" --json
  '

Observed output:

15:26:54 [agent/embedded] incomplete turn detected: runId=47c21d0f-283b-48c7-bd2f-ddd0b3fdb936 sessionId=47c21d0f-283b-48c7-bd2f-ddd0b3fdb936 stopReason=stop payloads=0 — surfacing error to user

{
  "ok": true,
  "capability": "model.run",
  "transport": "local",
  "provider": "openrouter",
  "model": "openrouter/free",
  "attempts": [],
  "outputs": [
    {
      "text": "⚠️ Agent couldn't generate a response. Please try again.",
      "mediaUrl": null
    }
  ]
}

Direct OpenRouter API call from the same environment succeeds with HTTP 200 and returns "smoke-ok".

Also verified that:
- https://openrouter.ai/api/v1/chat/completions is the correct working endpoint
- https://openrouter.ai/api/chat/completions does not return the API response and instead returns HTML / a site page
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Native OpenRouter integration fails for openrouter/openrouter/free with incomplete turn / payloads=0, even in a clean OpenClaw 2026.4.15 container.

Steps to reproduce

  1. Run a clean ephemeral OpenClaw container with valid OpenRouter credentials from an env file:

docker run --rm -it
--env-file /var/docker-skynet/.env
ghcr.io/openclaw/openclaw:2026.4.15
sh -lc ' openclaw models set openrouter/openrouter/free && openclaw infer model run --model openrouter/openrouter/free --prompt "Reply with exactly: smoke-ok" --json '

  1. Observe that OpenClaw returns:

    • [agent/embedded] incomplete turn detected ...
    • stopReason=stop
    • payloads=0
    • "⚠️ Agent couldn't generate a response. Please try again."
  2. Compare with a direct OpenRouter API call from the same environment:

curl -i -s https://openrouter.ai/api/v1/chat/completions
-H "Authorization: Bearer $OPENROUTER_API_KEY"
-H "Content-Type: application/json"
-d '{ "model":"openrouter/free", "messages":[{"role":"user","content":"Reply with exactly: smoke-ok"}] }'

This direct call succeeds and returns HTTP 200 with a valid assistant response.

Expected behavior

OpenClaw should successfully return the model response when using the native OpenRouter integration with openrouter/openrouter/free, especially when the same prompt succeeds through a direct OpenRouter API call from the same container/environment.

Actual behavior

OpenClaw's native OpenRouter path fails with:

[agent/embedded] incomplete turn detected ... stopReason=stop payloads=0

The CLI returns: "⚠️ Agent couldn't generate a response. Please try again."

This happens even in a clean ephemeral OpenClaw 2026.4.15 container with valid auth and without mounting any existing config or workspace.

OpenClaw version

2026.4.15

Operating system

Ubuntu 24.04

Install method

Docker

Model

openrouter/free

Provider / routing chain

Native OpenRouter integration in OpenClaw Model reference in OpenClaw: openrouter/openrouter/free Routing tested directly against OpenRouter API: https://openrouter.ai/api/v1/chat/completions

Additional provider/model setup details

  • OPENROUTER_API_KEY provided via --env-file /var/docker-skynet/.env
  • The same environment can call OpenRouter directly successfully
  • Reproduced in a clean ephemeral container, so this does not appear to depend on existing mounted config or persistent volumes
  • Also reproduced previously from the normal deployed container/UI/Telegram path, but the clean Docker repro above is the minimal case

Logs, screenshots, and evidence

Clean repro command:

docker run --rm -it \
  --env-file /var/docker-skynet/.env \
  ghcr.io/openclaw/openclaw:2026.4.15 \
  sh -lc '
    openclaw models set openrouter/openrouter/free &&
    openclaw infer model run --model openrouter/openrouter/free --prompt "Reply with exactly: smoke-ok" --json
  '

Observed output:

15:26:54 [agent/embedded] incomplete turn detected: runId=47c21d0f-283b-48c7-bd2f-ddd0b3fdb936 sessionId=47c21d0f-283b-48c7-bd2f-ddd0b3fdb936 stopReason=stop payloads=0 — surfacing error to user

{
  "ok": true,
  "capability": "model.run",
  "transport": "local",
  "provider": "openrouter",
  "model": "openrouter/free",
  "attempts": [],
  "outputs": [
    {
      "text": "⚠️ Agent couldn't generate a response. Please try again.",
      "mediaUrl": null
    }
  ]
}

Direct OpenRouter API call from the same environment succeeds with HTTP 200 and returns "smoke-ok".

Also verified that:
- https://openrouter.ai/api/v1/chat/completions is the correct working endpoint
- https://openrouter.ai/api/chat/completions does not return the API response and instead returns HTML / a site page

Impact and severity

High for native OpenRouter users. This makes the native OpenRouter integration unusable for openrouter/free in my environment, even though the underlying OpenRouter API itself is working correctly. It blocks using OpenClaw with native OpenRouter routing for this model.

Additional information

I specifically need to use OpenRouter natively in OpenClaw.

This issue is not limited to Telegram or the web UI:

  • It reproduced in Telegram
  • It reproduced in the OpenClaw UI
  • It reproduced in a clean ephemeral Docker container using only the CLI

That strongly suggests the issue is in OpenClaw's native OpenRouter execution path rather than in the client channel, my persisted config, or OpenRouter availability itself.

extent analysis

TL;DR

The native OpenRouter integration in OpenClaw fails with "incomplete turn detected" for the openrouter/openrouter/free model, despite the same prompt succeeding through a direct OpenRouter API call.

Guidance

  • Verify that the OpenRouter API endpoint used by OpenClaw is correct, as the direct API call to https://openrouter.ai/api/v1/chat/completions succeeds, but the issue occurs with the native integration.
  • Check the OpenClaw configuration and logs for any differences in how the OpenRouter API is called natively versus through the direct API call.
  • Test the native OpenRouter integration with a different model to determine if the issue is specific to the openrouter/openrouter/free model.
  • Compare the request payload and headers sent by OpenClaw's native integration with those sent by the direct API call to identify any discrepancies.

Example

No code snippet is provided as the issue seems to be related to the configuration or the native integration of OpenRouter in OpenClaw rather than a code-level problem.

Notes

The issue appears to be specific to the native OpenRouter integration in OpenClaw and not related to the OpenRouter API itself, as the direct API call succeeds. The problem is reproducible in different environments, including a clean ephemeral Docker container, which suggests that it is not related to persisted config or client channels.

Recommendation

Apply a workaround by using the direct OpenRouter API call until the native integration issue is resolved, as it has been verified to work correctly. This will allow for continued use of the OpenRouter model while the native integration problem is being addressed.

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

OpenClaw should successfully return the model response when using the native OpenRouter integration with openrouter/openrouter/free, especially when the same prompt succeeds through a direct OpenRouter API call from the same container/environment.

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]: native OpenRouter model openrouter/openrouter/free returns incomplete turn payloads=0 [1 pull requests, 3 comments, 3 participants]