You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
That commit added catalog rows so model_supports_reasoning("deepseek-flash") returns true. Verified — it does. It was the wrong gate. Reasoning still lands in visible prose, because the stream decoder classifies reasoning routes through a different function that was never touched.
crates/tui/src/client/chat.rs:3232:
fnrequires_reasoning_content(model:&str) -> bool{let lower = model.to_lowercase();// V4-family direct model IDs.
lower.contains("deepseek-v4")
|| lower.starts_with("deepseek-chat")
|| lower.starts_with("deepseek-reasoner")
|| has_deepseek_r_series_marker(&lower)}
deepseek-flash matches none of the four arms. It does not contain the literal deepseek-v4.
That result flows into is_reasoning_model_for_stream_on_route (:3391), which chooses ReasoningStreamStyle::SeparateField or ReasoningStreamStyle::None. With no match the style is None, so reasoning_content deltas are appended to answer text instead of becoming ContentBlock::Thinking.
Evidence from the founder's own session store
Every persisted session, counted by assistant content-block type:
model
thinking
text
tool_use
deepseek-v4.1-flash-ex… (11 sessions)
13, 8, 16, 12, 25, 11, 58, 338, 45, 15, 28
3, 7, 2, 11, 22, 1, 2, 192, 2, 1, 1
…
GLM-5.3
27
16
34
deepseek-flash (2 sessions)
0
35
58
deepseek-flash
0
40
68
20 of 23 sessions carry thinking blocks. The only two that carry none are the two deepseek-flash sessions — and every working model id contains the literal substring deepseek-v4. On healthy routes the ratio inverts (58 thinking / 2 text); on deepseek-flash it is 0 thinking / 35 text, with individual text blocks up to 73,003 characters of visible internal monologue.
Second failure mode: 400s on tool-call turns
The comment directly above the function states the replay requirement:
both have thinking mode enabled by default, so any assistant message carrying tool_calls must replay reasoning_content on subsequent turns or the API returns 400.
deepseek-flash has thinking on by default and its sessions carry 58 and 68 tool_use blocks. Because the route is not classified as reasoning, nothing is captured to replay, so those turns are exposed to the same 400. This is not only a display defect.
Root pattern
This is the third hand-maintained table holding a capability fact for the same model, each missed in turn:
model_supports_reasoning — catalog fallback pile (fixed in 10aa19570).
requires_reasoning_content — stream decoder substring test (this issue).
Fixing one does not fix the others, and each failure is silent. The 0.9.14 milestone names the durable answer: the catalog plus the cloud-facts channel owns capability, and these predicates read from it.
Fix shape
Immediate: make requires_reasoning_content recognise deepseek-flash. Prefer routing it through the catalog (model_supports_reasoning already returns true) over adding a fourth substring arm — a substring test on deepseek-v4 silently fails every future id that does not embed a version number, which is exactly how this happened.
Then add a test asserting that for each DeepSeek id the catalog marks reasoning-capable, reasoning_stream_style_for_route yields SeparateField and not None.
Verify by starting a fresh deepseek-flash session and confirming the assistant messages carry ContentBlock::Thinking, as reasoning_content_replayed_after_tool_call.rs does for deepseek-v4-pro.
Correction to the record
10aa19570's message asserts the leak was fixed. It was not; the fix addressed a gate that was not the one inlining the text. Session 5130a34d (2026-09-10) shows the leak with that commit installed.
Note on forensics: per-message created_at in the session journal is stamped at save time — all 103 entries in that session share 07:11:46.2346xx, incrementing by microseconds — so it cannot date individual messages. Worth fixing separately; it makes session files useless for sequencing.
This reopens what
10aa19570claimed to fixThat commit added catalog rows so
model_supports_reasoning("deepseek-flash")returns true. Verified — it does. It was the wrong gate. Reasoning still lands in visible prose, because the stream decoder classifies reasoning routes through a different function that was never touched.crates/tui/src/client/chat.rs:3232:deepseek-flashmatches none of the four arms. It does not contain the literaldeepseek-v4.That result flows into
is_reasoning_model_for_stream_on_route(:3391), which choosesReasoningStreamStyle::SeparateFieldorReasoningStreamStyle::None. With no match the style isNone, soreasoning_contentdeltas are appended to answer text instead of becomingContentBlock::Thinking.Evidence from the founder's own session store
Every persisted session, counted by assistant content-block type:
deepseek-v4.1-flash-ex…(11 sessions)GLM-5.3deepseek-flash(2 sessions)deepseek-flash20 of 23 sessions carry thinking blocks. The only two that carry none are the two
deepseek-flashsessions — and every working model id contains the literal substringdeepseek-v4. On healthy routes the ratio inverts (58 thinking / 2 text); ondeepseek-flashit is 0 thinking / 35 text, with individual text blocks up to 73,003 characters of visible internal monologue.Second failure mode: 400s on tool-call turns
The comment directly above the function states the replay requirement:
deepseek-flashhas thinking on by default and its sessions carry 58 and 68tool_useblocks. Because the route is not classified as reasoning, nothing is captured to replay, so those turns are exposed to the same 400. This is not only a display defect.Root pattern
This is the third hand-maintained table holding a capability fact for the same model, each missed in turn:
model_supports_reasoning— catalog fallback pile (fixed in10aa19570).route/capabilities.rs:96— provider route table (deepseek-flash is the declared default but is not registered for the deepseek provider, so it fails to resolve #6043, still open).requires_reasoning_content— stream decoder substring test (this issue).Fixing one does not fix the others, and each failure is silent. The 0.9.14 milestone names the durable answer: the catalog plus the cloud-facts channel owns capability, and these predicates read from it.
Fix shape
Immediate: make
requires_reasoning_contentrecognisedeepseek-flash. Prefer routing it through the catalog (model_supports_reasoningalready returns true) over adding a fourth substring arm — a substring test ondeepseek-v4silently fails every future id that does not embed a version number, which is exactly how this happened.Then add a test asserting that for each DeepSeek id the catalog marks reasoning-capable,
reasoning_stream_style_for_routeyieldsSeparateFieldand notNone.Verify by starting a fresh
deepseek-flashsession and confirming the assistant messages carryContentBlock::Thinking, asreasoning_content_replayed_after_tool_call.rsdoes fordeepseek-v4-pro.Correction to the record
10aa19570's message asserts the leak was fixed. It was not; the fix addressed a gate that was not the one inlining the text. Session5130a34d(2026-09-10) shows the leak with that commit installed.Note on forensics: per-message
created_atin the session journal is stamped at save time — all 103 entries in that session share07:11:46.2346xx, incrementing by microseconds — so it cannot date individual messages. Worth fixing separately; it makes session files useless for sequencing.