Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
Providers that stream reasoning in delta.reasoning (the OpenRouter
convention) have it concatenated onto the answer in a single text part,
with no thinking part — the same defect #95283 fixed for
reasoning_content.
Steps to reproduce
- Configure a custom
openai-completions provider for OVHcloud AI
Endpoints (https://oai.endpoints.kepler.ai.cloud.ovh.net/v1) with
model gpt-oss-20b, reasoning: true, no compat overrides.
- Run a turn whose answer is easy to compare exactly:
openclaw agent --model ovh/gpt-oss-20b --message 'Reply with exactly: ovh-ok'
- Read the stored assistant message parts.
Not deterministic: over one session's transcript, 2 of 3 turns split
correctly and 1 merged, same model, same version, same gateway. Repeat
the turn until a merged one appears.
Expected behavior
delta.reasoning opens and seals a thinking block exactly as
reasoning_content does after #95283, so the answer is delivered
separately:
parts=['thinking', 'text']
text='ovh-ok'
This is also what the same gateway produces on the turns that work.
Actual behavior
The reasoning and the answer arrive as one text part, no thinking part,
no separator:
parts=['text']
text='The user wants me to reply exactly "ovh-ok". No extra text.ovh-ok'
OpenClaw version
2026.9.2 (3928bad)
Operating system
FreeBSD 15.1-RELEASE-p3
Install method
npm install -g openclaw@latest, into an unprivileged user's ~/.local
Model
gpt-oss-20b
Provider / routing chain
Client -> local gateway -> OVHcloud AI Endpoints (https://oai.endpoints.kepler.ai.cloud.ovh.net/v1, api openai-completions). No proxy, no router, no TLS override.
Additional provider/model setup details
Custom provider block declaring the model with reasoning: true. No
compat block. request.proxy / request.tls unset. Streaming is on;
the request carries stream_options.include_usage: true.
Logs
# The provider separates the fields correctly. Three sampled streams,
# same request; identical shape each time:
delta keys observed: "content" "name" "reasoning" "role"
content -> "ovh-ok" # clean, 3/3
reasoning -> "The user says: ..." # separate field, 3/3
# Final chunk is spec-correct:
data: {"choices":[],"model":"gpt-oss-20b","object":"chat.completion.chunk",
"usage":{"prompt_tokens":68,"completion_tokens":8,"total_tokens":76}}
# What OpenClaw stored for a merged turn:
parts=['text']
text='The user wants me to reply exactly "ovh-ok". No extra text.ovh-ok'
# And for a correct turn, same model and version:
parts=['thinking', 'text'] thinkingSignature='reasoning'
text='ovh-ok'
Screenshots, recordings, and evidence
Counted over one session's stored transcript, filtered to this provider:
split (thinking+text) 2, text-only 1, empty 0.
Impact and severity
The visible answer is intermittently polluted with the model's own
reasoning. Anything consuming that text programmatically — a script, a
downstream tool, an automation parsing a short answer — receives a wrong
value rather than an obviously broken one, and only on some turns. Any
provider using the OpenRouter delta.reasoning convention is affected.
Additional information
#95280 reported the same shape for deepseek and #95283 fixed it by
sealing the open thinking block before visible text or a tool call
(merged 2026-06-22, released v2026.6.11, so present in the build tested
here). That fix seals reasoning_content deltas; this provider streams
delta.reasoning, so the same missing boundary remains on the other
field.
Two things that do not work as workarounds, both tested:
compat.thinkingFormat: "openrouter" changes the request rather than
the parse; the provider then rejects every call with "provider
rejected the request schema or tool payload".
- The model cannot be asked to stop reasoning:
reasoning_effort: "low"
is accepted and shortens the text but the field is still emitted, and
chat_template_kwargs returns HTTP 400.
Possibly the same code path: this provider also records zero token usage
(#75357, #56670) although the request sends
stream_options.include_usage and the stream returns the usage chunk
quoted above. In the same session, anthropic-messages turns recorded
61 tokens and $0.036 while the openai-completions turn beside them
recorded 0.
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
Providers that stream reasoning in
delta.reasoning(the OpenRouterconvention) have it concatenated onto the answer in a single text part,
with no thinking part — the same defect #95283 fixed for
reasoning_content.Steps to reproduce
openai-completionsprovider for OVHcloud AIEndpoints (
https://oai.endpoints.kepler.ai.cloud.ovh.net/v1) withmodel
gpt-oss-20b,reasoning: true, nocompatoverrides.openclaw agent --model ovh/gpt-oss-20b --message 'Reply with exactly: ovh-ok'Not deterministic: over one session's transcript, 2 of 3 turns split
correctly and 1 merged, same model, same version, same gateway. Repeat
the turn until a merged one appears.
Expected behavior
delta.reasoningopens and seals a thinking block exactly asreasoning_contentdoes after #95283, so the answer is deliveredseparately:
This is also what the same gateway produces on the turns that work.
Actual behavior
The reasoning and the answer arrive as one text part, no thinking part,
no separator:
OpenClaw version
2026.9.2 (3928bad)
Operating system
FreeBSD 15.1-RELEASE-p3
Install method
npm install -g openclaw@latest, into an unprivileged user's ~/.local
Model
gpt-oss-20b
Provider / routing chain
Client -> local gateway -> OVHcloud AI Endpoints (
https://oai.endpoints.kepler.ai.cloud.ovh.net/v1, apiopenai-completions). No proxy, no router, no TLS override.Additional provider/model setup details
Custom provider block declaring the model with
reasoning: true. Nocompatblock.request.proxy/request.tlsunset. Streaming is on;the request carries
stream_options.include_usage: true.Logs
Screenshots, recordings, and evidence
Counted over one session's stored transcript, filtered to this provider:
split (thinking+text) 2, text-only 1, empty 0.
Impact and severity
The visible answer is intermittently polluted with the model's own
reasoning. Anything consuming that text programmatically — a script, a
downstream tool, an automation parsing a short answer — receives a wrong
value rather than an obviously broken one, and only on some turns. Any
provider using the OpenRouter
delta.reasoningconvention is affected.Additional information
#95280 reported the same shape for deepseek and #95283 fixed it by
sealing the open thinking block before visible text or a tool call
(merged 2026-06-22, released v2026.6.11, so present in the build tested
here). That fix seals
reasoning_contentdeltas; this provider streamsdelta.reasoning, so the same missing boundary remains on the otherfield.
Two things that do not work as workarounds, both tested:
compat.thinkingFormat: "openrouter"changes the request rather thanthe parse; the provider then rejects every call with "provider
rejected the request schema or tool payload".
reasoning_effort: "low"is accepted and shortens the text but the field is still emitted, and
chat_template_kwargsreturns HTTP 400.Possibly the same code path: this provider also records zero token usage
(#75357, #56670) although the request sends
stream_options.include_usageand the stream returns the usage chunkquoted above. In the same session,
anthropic-messagesturns recorded61 tokens and $0.036 while the
openai-completionsturn beside themrecorded 0.