Repository navigation
Description
Summary
On the Provider API (/provider/v1/chat/completions), the historical reasoning_content of an assistant message is dropped by key name before the request is serialized upstream, for these slugs:
deepseek/deepseek-v4.1-flashdeepseek/deepseek-v4-flashdeepseek/deepseek-v4-flash-vision-exp
It works correctly (reasoning_content is forwarded and re-inserted into context) for deepseek/deepseek-v4-pro and deepseek/deepseek-v4-flash-fast, and for the same request body against https://api.deepseek.com/v1.
Per DeepSeek's thinking-mode guide, when the request carries tools, the reasoning_content of every historical assistant message must be passed back and is re-inserted into context. Because it is dropped here, in a tool-calling loop the model can no longer see its own earlier chain of thought, so it re-derives its reasoning from scratch every turn. This degrades multi-turn coherence and is easy to misread as the model "getting dumber" on longer tasks.
The gateway evidently recognizes the field, so it isn't an unknown-key pass-through issue: sending a non-string value returns a clean schema error naming the exact parameter:
HTTP 400 {"error":{"message":"Invalid input: expected string, received object",
"type":"invalid_request_error",
"param":"messages.1.reasoning_content"}}
The same applies to reasoning (expected string, received number) and reasoning_details (expected array, received string). So the field is validated... and then stripped for those three slugs.
Expected Behavior
With tools present in the request, reasoning_content on historical assistant messages should be forwarded to the upstream model and re-inserted into context, as DeepSeek documents and as deepseek-v4-pro / deepseek-v4-flash-fast already do on the same gateway.
Actual Behavior
The field is schema-validated and then dropped. It contributes zero to the upstream prompt, while a mirrored copy of the exact same text in content counts in full.
Primary evidence is prompt_tokens accounting (3000-char reasoning block ≈ 1516 tokens), paired within a run:
request body (tool-loop shape, tools present) |
prompt_tokens | delta |
|---|---|---|
assistant message with no reasoning_content |
352 | — |
reasoning_content = 3000 chars |
352 | +0 |
reasoning = 3000 chars |
352 | +0 |
reasoning_details = [{...3000 chars}] |
352 | +0 |
control: the same 3000 chars in content |
1868 | +1516 |
Behavioural confirmation (marker test, see repro below): a random 6-char code embedded in the historical reasoning cannot be repeated back by the model, while the identical code placed in content can.
Cross-model matrix — identical request body, tools present:
| model | historical reasoning reaches upstream |
|---|---|
deepseek/deepseek-v4.1-flash |
❌ dropped (352 → 352) |
deepseek/deepseek-v4-flash |
❌ dropped |
deepseek/deepseek-v4-flash-vision-exp |
❌ dropped |
deepseek/deepseek-v4-pro |
✅ forwarded (421 → 1937) |
deepseek/deepseek-v4-flash-fast |
✅ forwarded |
https://api.deepseek.com/v1 (deepseek-v4-pro, same body) |
✅ forwarded |
Steps to reproduce the issue
1. Marker test — embed a known code in historical reasoning, then ask for it back:
curl -s https://api.commandcode.ai/provider/v1/chat/completions \
-H "Authorization: Bearer $COMMANDCODE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-v4.1-flash",
"tools": [{"type":"function","function":{"name":"get_weather","description":"w",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}],
"messages": [
{"role":"user","content":"Check Beijing weather with get_weather."},
{"role":"assistant",
"reasoning_content":"Internal check: the session code is ZQ7K2M. I must repeat it when asked.",
"tool_calls":[{"id":"c1","type":"function","function":{"name":"get_weather","arguments":"{\"city\":\"Beijing\"}"}}]},
{"role":"tool","tool_call_id":"c1","content":"25C clear"},
{"role":"user","content":"Repeat the session code from your earlier reasoning. Output only those 6 characters."}
]}'- Expected:
ZQ7K2M— this is whatapi.deepseek.comreturns for the identical body, and what this gateway returns fordeepseek-v4-pro/deepseek-v4-flash-fast. - Actual: the model replies that it never recorded any code.
2. Token accounting — repeat the same request twice, once with and once without reasoning_content on the assistant message, and compare usage.prompt_tokens. The value does not move for the three slugs above (+0), while inserting the same text into content moves it by the expected amount.
Note: the marker test alone is not fully deterministic (the model occasionally declines even when the block clearly arrived), so
prompt_tokensis the primary signal. The delta is paired within a run and stable across runs (alternated 3× onv4.1-flash: +0 without the header, +1516 with it).
Command Code Version
n/a — Provider API (/provider/v1) only; no CLI involved.
Operating System
Linux
Terminal/IDE
n/a — no CLI/IDE involved; raw HTTP requests against /provider/v1
Shell
n/a — repro is raw HTTP (curl/httpx), not an interactive shell session
Session file (optional)
No response
Fix prompt (optional)
On the Provider API (/provider/v1), historical assistant reasoning_content is stripped by key
name before the request reaches the upstream, for deepseek/deepseek-v4.1-flash,
deepseek/deepseek-v4-flash, and deepseek/deepseek-v4-flash-vision-exp. It works for
v4-pro and v4-flash-fast. DeepSeek requires the field to be replayed when tools is present.
Where to look:
- The request-serialization path for the affected slugs' default (non-ZDR) upstream route.
The key is schema-validated (a non-string returns 400 namingmessages.N.reasoning_content),
so it is recognized and then dropped during serialization — not an unknown-key pass-through. deepseek-v4-flashis affected on BOTH the default and ZDR routes, so treat it as a separate
entry from thev4.1-flash/v4-flash-vision-exppair (at least two code paths).- Diff the serialization against the slugs that work (
v4-pro,v4-flash-fast), and against
api.deepseek.com for the same body.
Correct behaviour: with tools present, pass reasoning_content on historical assistant
messages through unchanged (and re-insert it into context upstream).
How to check:
- Send the same tool-loop body twice — with and without a 3000-char
reasoning_contenton the
assistant message — and compareusage.prompt_tokens. It must grow by the block's token count;
today it is byte-identical (+0) for the three affected slugs. - Discriminator: also put the same 3000 chars in
content. Ifcontentcounts but
reasoning_contentstill adds 0 in the SAME message, the strip is key-level, not a rewrite. - Behavioural check (optional): embed a random 6-char code in the historical reasoning and ask
the model to repeat it. Not fully deterministic — prefer the token delta.
Additional context
Analysis — the drop is a key-name strip, not a message rewrite
Putting a mirrored copy in content and the real value in reasoning_content in the same assistant message discriminates the two possibilities (3000-char block ≈ 1516 tokens):
| route | model | A: base | B: rc only | C: content only | D: both | D − C |
|---|---|---|---|---|---|---|
| default | v4.1-flash |
352 | 352 (+0) | 1868 (+1516) | 1868 | +0 |
| default | v4-pro |
421 | 1937 (+1516) | 1937 (+1516) | 3453 | +1516 |
| ZDR | v4.1-flash |
352 | 1868 (+1516) | 1868 (+1516) | 3384 | +1516 |
| ZDR | v4-pro |
343 | 1859 (+1516) | 1859 (+1516) | 3375 | +1516 |
On the default route for v4.1-flash, the content copy counts in full while reasoning_content adds exactly 0 — in the same message. The message itself is not rewritten; the key is stripped by name.
The drop is route-specific, not model-specific. With x-cmd-zdr: 1, the same request on the same slug forwards the field correctly (352 → 1868). Since ZDR only changes which upstream serves the request, the filter appears to live in the non-ZDR upstream's request adapter — and deepseek-v4-flash is +0 on both routes, so at least two separate code paths are involved.
Related but separate: on a tool-role message a reasoning_content key is silently accepted (HTTP 200) instead of being schema-rejected, so the validation is asymmetric as well.
Additional context
- Plan: GOAT. Reproduced via raw
curl/httpxagainst/provider/v1, and againstapi.deepseek.com/v1as a control. - Reported and triaged in the Discord bug thread first; staff asked for a GitHub issue with the repro.
- A workaround exists but has real costs, which is why this needs a fix on the default route rather than a client-side flag: forcing
x-cmd-zdr: 1restores forwarding, but per the ZDR docs it meters at the default allowance (thedeepseek-v4.1-flashboosted allowance drops from $60 to $20 on GOAT), routes may be pricier, and it is strict — no fallback, a bare422 cmd_zdr_no_providersif ZDR capacity is unavailable. - Related: Provider API: deepseek-v4.1-flash is missing reasoning effort metadata #837 (
deepseek-v4.1-flashmissing reasoning-effort metadata) is a different symptom on the same slug; both look like per-slug provider metadata/routing entries. - Nothing in the docs describes how
reasoning_contenton historical assistant messages is handled per slug, so there is no documented behaviour to hold clients to here — treating this as a backend fault.
Activity
ahmadawais commentedon Sep 11, 2026
@ahmadbilaldev can you check.
ahmadawais commentedon Sep 11, 2026
I believe this was fixed.
From Command Code I got this:
0e472e2(2026-09-08, inorigin/main) declares all three fields and maps them per-provider. I ran their exact body throughreasoning-echo.tsandreasoning_contentcomes out intact.Nothing to fix in this repo.
AkaneRanma commentedon Sep 11, 2026
Re-tested against production after your comment — the affected slugs still drop the field on the default route.
All timestamps below come from your edge's own
dateheader (my host clock matches it), and the requests are plain HTTP against/provider/v1. No CLI is in the path, so there is nothing client-side to update here.1. New oracle: the drop is measurable in tokens
Replace the field's value with a block far larger than anything I'd normally replay, and read
usage.prompt_tokens:deepseek-v4.1-flashdeepseek-v4.1-flashdeepseek-v4-flashdeepseek-v4-flashdeepseek-v4-prodeepseek-v4-flash-fastOn the working routes the block is counted to the token, so the text demonstrably reaches the model's tokenizer. On the affected default routes a 600 KB block adds exactly zero — and it isn't a length cap, since a 3 KB block also adds 0 (matrix in the issue body).
So the removal happens before tokenization, at a point selected by the route/provider entry — not model behaviour, not a size threshold. That still doesn't by itself separate "your edge" from "the wire slug's provider", but it does mean the fix has to land in whatever construct picks that route.
2. What "fixed several days ago" refers to
Your changelog header says it covers "Command Code CLI and Desktop" — and in the 9/8–9/9 window there is no entry for reasoning forwarding at all. The only v4.1-flash entries are:
v1.51.0(Sep 8) — Add DeepSeek V4.1 Flash Betav1.51.2(Sep 9) — Remove DeepSeek V4.1 Flash Betav1.53.0(Sep 9, published to npm 2026-09-10T06:13Z) — Add new deepseek/deepseek-v4.1-flash model0e472e2is dated 2026-09-08, which lines up with adding that model to the CLI rather than with a gateway change. That reading matches your description exactly — "declares all three fields and maps them per-provider" is what a client does when it starts supporting a model. If so,reasoning-echo.tsis exercising the CLI's own request construction, and nothing in it touches the inbound path a third-party body takes.To be precise about what that test can show: a passing unit test on the transform function proves the field survives your mapping code. It can't show what the deployed gateway does with a request on a given route — the two are different layers, and "unit test green + production still drops it" is a perfectly consistent state.
3. Client identity isn't the variable
I also checked whether the gateway special-cases its own CLI — same request, different identity headers:
User-Agent: command-code/1.53.0User-Agent: CommandCode/1.53.0+x-cmd-client: cliUser-Agent: command-code/1.53.0+x-cmd-version: 1.53.0User-Agent: OpenAI/NodeJS/4.0.0x-cmd-zdr: 1(control)So it isn't the client, and it isn't "old CLI version" — the only thing that changes behaviour is the route. Pointing a client at the latest CLI build can't fix a request that never goes through the CLI.
4. Two things that would settle the location
0e472e2?origin/mainisn't checkable from outside. The public repo has 22 entries total — issue templates, logos, stickers,readme.md, and no code at all (verified via the git tree API), and the SHA returns422 No commit foundthere. So "nothing to fix in this repo" is trivially true and can't say anything about the gateway;0e472e2must live in an internal repo. If prod is a build from before that commit, the observed behaviour is exactly what a deployment lag would look like.v4-flashis broken on both routes (a second, independent path).5. "v4-pro recalls
ZQ7K2Mthrough the identical code path, so we're not the one dropping it"Same code path, different per-provider entry — and that entry is the variable. These are not the same upstream: identical tool-loop bodies tokenize to different prompts per route (v4-pro 421 default / 343 ZDR; v4.1-flash 352), and
v4-flash-fastreturns a visibly different envelope (bare hex id, nosystem_fingerprint, extrametadata). v4-pro working on its own entry doesn't tell us thedeepseek/deepseek-v4.1-flashwire-slug entry is fine.6. Possible scope mismatch worth checking on your side
If
0e472e2is a CLI-side fix — "declares all three fields and maps them per-provider" reads like client request-schema work — then it doesn't cover this report. The repro is a third-party client sending DeepSeek's native field name straight to/provider/v1; no CLI is involved, and the drop is visible in your own response'susage.prompt_tokens. If the CLI's request schema now declares the fields but the gateway's inbound path for the same body still drops them by name, both can be true at once.Current ask
/provider/v1today (does it contain0e472e2?).Until then, the practical workaround for clients is unchanged: ZDR header on the affected slugs, or
v4-pro/v4-flash-fast(which don't have native vision).Probes are plain
httpxscripts (600 KB oracle + D−C discriminator) — happy to paste them here if useful.Two accuracy notes, since they cut against the report as much as for it:
api.deepseek.comreturned 200 for this shape withoutreasoning_content(deepseek-flashanddeepseek-v4-pro). So the documented 400 isn't universally enforced — the reason this matters is the functional one: withtoolspresent the field is concatenated into context, which is what ZDR demonstrably restores and the affected routes demonstrably lose.prompt_tokensis the primary signal, which is why the 600 KB oracle above is quoted instead of the recall test.cyx-slps commentedon Sep 11, 2026
Duplicate report — same root cause as #754 and #829; still reproducible by multiple independent users
This issue is the same defect as:
reasoning_contenton tool-call continuation turns (#754 not fixed) #829 — OpenAI-compatible v1 endpoint still drops echoed assistantreasoning_contenton tool-call continuation turns ([Bug][Critical] Reasoning echo-back silently dropped across ALL thinking models (DeepSeek/GLM/Kimi/MiMo…) — multi-turn agents hallucinate fake memories & suffer severe quality degradation #754 not fixed) — openAll three describe one thing: echoed historical reasoning (
reasoning_content/reasoningon an assistant message) never reaches the upstream model, so in a tool loop the model cannot continue from its own earlier chain of thought and re-derives or fabricates instead. The reports differ only in observation surface — #754 / #829 measure it behaviourally (0/N exact recall), this issue measures the same drop inusage.prompt_tokens(+0). The route-dependence finding here (ZDR forwards the field, default route does not) is new detail on that one root cause, not a separate bug.Reporters still reproducing it after the 9/7 "fixed"
/provider/v1+/alpha/generate, DeepSeek and GLM-5.2/provider/v1— v4.1-flash (reasoning_content×3,reasoning×2), v4-flash ×2/provider/v1— v4.1-flash 0/5, v4-flash 0/4 (plusreasoning/ content-parts variants, all 0/4)/alpha/generateclean probe 0/3;/provider/v1clean self-generated marker 0/2 (v4-flash), 0/3 (v4.1-flash)/provider/v1— 600 KB block on the 3 affected slugsapi.deepseek.com, byte-identical body, v4-flashSix independent reporters, four different methods (behavioural recall, clean self-generated marker, token accounting, per-field-name variants), on both
/alpha/generateand/provider/v1, across 4+ model slugs — all fail, while the DeepSeek official control with the identical request body passes every round.Timeline that matters
0e472e2cited here as the fix.0e472e2comment: still+0.A closed issue plus an internal-only commit SHA cannot be checked from outside (the public repo carries no code and that SHA returns
422 No commit found), so "fixed" currently has no observable artefact attached to it.Requests
reasoning_contenton tool-call continuation turns (#754 not fixed) #829 + Provider API: historical reasoning_content is dropped by name for deepseek-v4.1-flash / v4-flash / v4-flash-vision-exp #840 as one tracking item — reopen [Bug][Critical] Reasoning echo-back silently dropped across ALL thinking models (DeepSeek/GLM/Kimi/MiMo…) — multi-turn agents hallucinate fake memories & suffer severe quality degradation #754 or consolidate — so the status isn't split between a closed issue claiming fixed and two open issues reproducing it.Related but distinct (same per-slug provider-entry area, different symptoms): #807 (content-parts array dropped on tool-call turns) and #837 (v4.1-flash reasoning-effort metadata).
ahmadbilaldev commentedon Sep 11, 2026
Taking a look
pppopipupu commentedon Sep 11, 2026
I found a temporary workaround: you can either manually modify the client to insert the contents of reasoning_content into content before sending the request, or enable the built-in requiresThinkingAsText flag in Pi agent.
ahmadbilaldev commentedon Sep 11, 2026
Upstream issue, getting it fixed.
ahmadbilaldev commentedon Sep 11, 2026
Should be fixed now. Thanks for reporting.