feat: return readable Claude reasoning, and render absent reasoning honestly - #308
Merged
Merged
Conversation
…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>
rsharath
approved these changes
Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.displaydefaults 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:
thinkingtextClaudeProvidernow passesdisplay: "summarized".This is free.
displaycontrols 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 as1 lines, and opened onto the placeholder — which reads as a counting bug rather than absent data.(N lines)(N lines)/(1 line)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-ledgerhas 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_UNAVAILABLEin 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+typecheckclean, 2382 pass / 0 fail.Web:
typecheck+buildclean, 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