…onestly
The reasoning expander in the web UI always read "reasoning (1 lines)" and
expanded onto nothing. Two independent causes.
1. Claude was never asked for readable reasoning.
`thinking.display` defaults to "omitted" on Opus 5 / 4.8 / 4.7, Sonnet 5, and
Fable 5, which streams thinking blocks whose text is EMPTY; codeoid then
committed them as a placeholder. Measured on one live session: 3,445 of 3,450
thinking records were the placeholder, and 635 of 635 raw SDK thinking blocks
across six transcripts had empty text.
ClaudeProvider now passes `display: "summarized"`. This is free — display
controls visibility only, and thinking is billed identically either way, so
the tokens were already being spent with nothing shown for them. (The raw
chain of thought is never exposed on any model; this is a summary of it.)
2. The UI promised content it could not have.
Rendering a <details> expander over the placeholder advertised reasoning,
counted it as "1 lines", and opened onto the placeholder text — which reads as
a counting bug rather than absent data. It now renders a flat, non-interactive
marker when there is nothing behind it, and keeps the expander (with a correct,
correctly-pluralised count) when there is.
Both branches are live and neither is an error state, which is why this is a
render-time decision rather than a backend fix: qwen-code and OSS models behind
an OpenAI-compatible gateway stream plaintext reasoning today — qwen-ledger has
real thinking content in its transcript — while Claude only does so under the
setting above.
The placeholder moves to a shared `REASONING_UNAVAILABLE` constant in the
protocol package: the daemon writes it and the client keys off it, so the two
sides cannot drift over a prose string. Its wording changes from "(reasoning
elided)" — codeoid elides nothing; the backend returned none.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reasoning expander always read "reasoning (1 lines)" and expanded onto nothing. Two independent causes — one API, one UI.
1. Claude was never asked for readable reasoning
thinking.display defaults to "omitted" on Opus 5 / 4.8 / 4.7, Sonnet 5, and Fable 5 — which streams thinking blocks whose text is empty. codeoid then committed them as a placeholder.
Measured on live data:
ClaudeProvider now passes display: "summarized".
This is free. display controls visibility only — thinking happens and is billed identically either way. The tokens were already being spent; we just weren't being shown anything for them. (The raw chain of thought is never exposed on any model; this is a summary of it.)
2. The UI promised content it could not have
A <details> expander over the placeholder advertised reasoning, counted it as 1 lines, and opened onto the placeholder — which reads as a counting bug rather than absent data.
Both branches are live, and neither is an error state — which is why this is a render-time decision rather than something to "fix" in a backend. qwen-code and OSS models behind an OpenAI-compatible gateway stream plaintext reasoning today (qwen-ledger has real thinking content in its transcript), while Claude only does under the setting above. The same UI now serves both.
Pluralisation is fixed as a side effect — (1 line), not (1 lines).
Shared constant
The placeholder moves to REASONING_UNAVAILABLE in the protocol package. The daemon writes it and the client keys off it, so the two sides can't drift over a prose string — the same class of bug as the memory-server name in #307.
Its wording also changes from "(reasoning elided)": codeoid elides nothing, the backend returned none. The old phrasing pointed the finger in the wrong direction while debugging this.
Verification
Daemon: lint + typecheck clean, 2382 pass / 0 fail.
Web: typecheck + build clean, 425 pass / 0 fail (41 files) — 3 new tests covering the absent-reasoning marker, the present-reasoning expander, and whitespace-only treated as absent; 3 existing assertions updated for the pluralisation fix.
🤖 Generated with Claude Code