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
{{ message }}
Repository navigation
reasoning: long reasoning is typed as text and leaks the closing </think> delimiter #56428
Assistant reasoning is classified inconsistently. Short reasoning is stored/rendered as a reasoning content part, but longer reasoning is fragmented into text parts and the reasoning→answer delimiter leaks into the visible assistant text. The opening delimiter never appears, so a long reasoning block shows only its closing delimiter.
Reasoning should be stored/rendered as a single reasoning unit delimited by ... , for any length. The closing delimiter should be consumed at the reasoning→answer boundary and never appear in the assistant's visible text. The delimiters are the byte sequences:
open: 3c 74 68 69 6e 6b 3e
close: 3c 2f 74 68 69 6e 6b 3e
i.e. .
Actual Behavior
Short reasoning blocks are correct, long ones are not.
Short turns produce a clean reasoning part (no delimiters), e.g.:
seq 7: reasoning:41
seq 20: reasoning:219 + clean text:217
Long turns produce a single text part whose body is <reasoning text></think><answer text>, with no opening delimiter, e.g.:
seq 35: text:1095, close delimiter at byte offset 891
seq 48: text:195, close delimiter at offset 80
seq 57: text:2230, close delimiter at offset 1509
Some long turns are split into multiple parts of the same reasoning stream with inconsistent types, e.g. seq 459: text:7838 followed by reasoning:574.
There are parts that consist of nothing but the closing delimiter: seq 92 and seq 174 are 9-byte text parts equal to plus a newline.
The opening delimiter appears as the first bytes of no content part (0 occurrences).
Counts for the affected session ses_eb6044450ffeMO126Z5pJ5rhhK:
metric
value
assistant messages
63
... containing the close delimiter
61
... containing the open delimiter
12 (quotations inside reasoning text, not structural)
content parts total
134
parts typed reasoning
8
parts typed text containing the close delimiter
61
close-only (text = delimiter only) parts
3
parts beginning with the open delimiter
0
Global (all sessions in the DB): 364 parts typed reasoning vs 118 parts typed text containing the closing delimiter.
Net effect: for a long reasoning block the opening delimiter is gone while the closing delimiter remains, so the UI shows a dangling close marker (and the reasoning is surfaced as normal assistant text instead of a reasoning block). For short blocks both delimiters are handled and it renders correctly.
Additional Context
The stored form of a broken turn is one text part: <reasoning text></think><answer text>. The closing delimiter sits exactly on the reasoning→answer boundary (verified byte offsets above), but the reasoning before it is typed text rather than reasoning.
The open delimiter (hex 3c 74 68 69 6e 6b 3e) never appears on the assistant side; only the close delimiter (hex 3c 2f 74 68 69 6e 6b 3e) does.
Working vs broken correlates with block size: the only correctly split reasoning parts in the session are the two shortest (41 and 219 chars); every larger block stayed as text with the leaked delimiter. This looks like delimiter handling that succeeds for a small/single-chunk block but fails once reasoning spans multiple stream chunks.
No plugins are configured, so this is not plugin-induced.
DB inspected read-only at ~/.local/share/opencode/opencode.db (session_message.data.content[]).
Thanks for the detailed report and the byte-level measurements, @Bad0Weather.
I think this is a duplicate of #54599. In both, opencode/big-pickle sends its reasoning inline in the reply, ending in a literal </think> with no opening tag, and OpenCode saves it as a normal text part instead of a reasoning part. Comments there describe the same pattern you saw: some turns get a proper reasoning part and others leak, and the opening tag never shows up. PR #54612 proposes a fix that splits the inline reasoning out where the stream is parsed.
Possible duplicates and related issues, ordered by likelihood:
Adding the per-session counts from my duplicate report (#56428), as requested.
Environment: opencode v2.0.31 (Arch Linux), model opencode/big-pickle (provider opencode, variant default, agent build). Session:ses_eb6044450ffeMO126Z5pJ5rhhK. Measured on session_message.data.content[] (V2 schema).
Counts
metric
value
assistant messages
63
... containing the close marker
61
... containing the open marker
12 (all quotations inside CoT text, not structural)
content parts total
134
parts typed reasoning
8
parts typed text containing the close marker
61
parts that are only the close marker (9 bytes: marker + \n)
3
parts whose first bytes are the open marker
0
Short turns correct, long turns leak
In this session the split works only for the very smallest CoT blocks; everything larger leaks. Stored shape of a leaked turn is a single text part: <CoT></think><answer> — the close marker sits exactly on the CoT→answer boundary, but the CoT before it is typed text, and there is no opening marker anywhere.
seq
content parts
close-marker byte offset
result
7
reasoning:41
—
correct (collapsible reasoning part)
20
reasoning:219, text:217
—
correct
48
text:195
80
leaked
35
text:1095
891
leaked
57
text:2230
1509
leaked
So the mid-session flip others described shows up here as a size threshold: seq 7 and 20 (CoT 41 / 219 chars) parse as reasoning; from seq 35 onward (CoT ≥ ~891 chars) nothing does, and the close marker leaks into text.
Inconsistent split within a single turn
seq 459 has text:7838 immediately followed by reasoning:574 — two consecutive chunks of the same CoT stream typed differently. Small reasoning fragments also appear on otherwise-leaked turns (seq 279:5, seq 430:9, seq 591:52), which look like parser artifacts rather than real reasoning parts.
Global
Across all sessions in the same DB: 364 parts typed reasoning vs 118 parts typed text containing the close marker.
Happy to attach the raw sqlite3 output / a session export if useful.
Thanks for the extra measurements, @Bad0Weather. The size threshold (short CoT parsed as reasoning, longer CoT leaking into text with </think> at the boundary) and the mixed types within one turn in seq 459 are useful details.
It looks like this comment was posted here on #56428, not on #54599. That issue is where the fix is being tracked and where PR #54612 is being reviewed. Could you post the same comment there so it reaches the people working on it? Raw sqlite3 output or a session export would also be most useful on #54599.
Nothing in these numbers suggests a different problem from #54599, so the bot will still close this issue as a duplicate as planned.
Closing as a duplicate of #54599, since nothing here points to a different problem. Both are about opencode/big-pickle sending its reasoning inline with a literal </think> and no opening tag, which OpenCode then saves as visible text instead of a reasoning part. The proposed fix is PR #54612.
Thanks again for the detailed counts, @Bad0Weather. The size threshold and the mixed types within one turn in seq 459 would help most on #54599, so please add them there if you haven't yet. If this turns out to be a different problem, reply here and we can reopen.
Activity
opencode-agent commentedon Oct 17, 2026
Thanks for the detailed report and the byte-level measurements, @Bad0Weather.
I think this is a duplicate of #54599. In both,
opencode/big-picklesends its reasoning inline in the reply, ending in a literal</think>with no opening tag, and OpenCode saves it as a normaltextpart instead of areasoningpart. Comments there describe the same pattern you saw: some turns get a proper reasoning part and others leak, and the opening tag never shows up. PR #54612 proposes a fix that splits the inline reasoning out where the stream is parsed.Possible duplicates and related issues, ordered by likelihood:
</think>saved astext); reproduced on v2, with fix PR fix(core): split inline chain-of-thought into a reasoning part #54612opencode/big-pickle, already closed as a duplicate of reasoning: opencode zen (big-pickle) inline </think> leaks into text content instead of a reasoning part #54599textwith noreasoningpart in long sessions, marked as a duplicate of reasoning: opencode zen (big-pickle) inline </think> leaks into text content instead of a reasoning part #54599Your per-session counts (short turns correct, long turns leaking) would be useful on #54599, so please add them there as a comment.
The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different.
Bad0Weather commentedon Oct 17, 2026
Adding the per-session counts from my duplicate report (#56428), as requested.
Environment: opencode v2.0.31 (Arch Linux), model
opencode/big-pickle(provideropencode, variantdefault, agentbuild).Session:
ses_eb6044450ffeMO126Z5pJ5rhhK. Measured onsession_message.data.content[](V2 schema).Counts
reasoningtextcontaining the close marker\n)Short turns correct, long turns leak
In this session the split works only for the very smallest CoT blocks; everything larger leaks. Stored shape of a leaked turn is a single
textpart:<CoT></think><answer>— the close marker sits exactly on the CoT→answer boundary, but the CoT before it is typedtext, and there is no opening marker anywhere.reasoning:41reasoning:219,text:217text:195text:1095text:2230So the mid-session flip others described shows up here as a size threshold: seq 7 and 20 (CoT 41 / 219 chars) parse as
reasoning; from seq 35 onward (CoT ≥ ~891 chars) nothing does, and the close marker leaks intotext.Inconsistent split within a single turn
seq 459hastext:7838immediately followed byreasoning:574— two consecutive chunks of the same CoT stream typed differently. Smallreasoningfragments also appear on otherwise-leaked turns (seq 279:5,seq 430:9,seq 591:52), which look like parser artifacts rather than real reasoning parts.Global
Across all sessions in the same DB: 364 parts typed
reasoningvs 118 parts typedtextcontaining the close marker.Happy to attach the raw
sqlite3output / a session export if useful.opencode-agent commentedon Oct 17, 2026
Thanks for the extra measurements, @Bad0Weather. The size threshold (short CoT parsed as
reasoning, longer CoT leaking intotextwith</think>at the boundary) and the mixed types within one turn inseq 459are useful details.It looks like this comment was posted here on #56428, not on #54599. That issue is where the fix is being tracked and where PR #54612 is being reviewed. Could you post the same comment there so it reaches the people working on it? Raw
sqlite3output or a session export would also be most useful on #54599.Nothing in these numbers suggests a different problem from #54599, so the bot will still close this issue as a duplicate as planned.
opencode-agent commentedon Oct 18, 2026
Closing as a duplicate of #54599, since nothing here points to a different problem. Both are about
opencode/big-picklesending its reasoning inline with a literal</think>and no opening tag, which OpenCode then saves as visibletextinstead of areasoningpart. The proposed fix is PR #54612.Thanks again for the detailed counts, @Bad0Weather. The size threshold and the mixed types within one turn in
seq 459would help most on #54599, so please add them there if you haven't yet. If this turns out to be a different problem, reply here and we can reopen.