Repository navigation
Description
Summary
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.
Environment
- opencode version: v2.0.31 (Arch package
opencode2.0.31-1) - OS: Linux 7.2.10-arch1-1 (Arch Linux), x86_64
- Terminal: TERM=xterm-256color, COLORTERM=truecolor (alacritty)
- Shell: /usr/bin/bash
- Install/channel: Arch Linux package (
extra), upstream https://github.com/anomalyco/opencode - Active plugins: none found (no
~/.config/opencode/pluginorpluginsdir;opencode.jsonccontains only$schema)
Model used in the affected session: provider opencode, model big-pickle, variant default, agent build.
Reproduction
- Start a session with a model that emits reasoning (here:
opencode/big-pickle). - Send a prompt that yields a short reasoning block (a few hundred chars).
- Send a prompt that yields a long reasoning block (several KB).
- Observe the assistant output, and/or inspect
~/.local/share/opencode/opencode.db.
Inspect with (read-only copy recommended):
sqlite3 opencode.db "SELECT sm.seq, json_extract(j.value,'$.type'),
length(json_extract(j.value,'$.text'))
FROM session_message sm, json_each(sm.data,'$.content') j
WHERE sm.session_id='<session>' AND sm.type='assistant';"Expected Behavior
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
reasoningpart (no delimiters), e.g.:seq 7:reasoning:41seq 20:reasoning:219+ cleantext:217
- Long turns produce a single
textpart whose body is<reasoning text></think><answer text>, with no opening delimiter, e.g.:seq 35:text:1095, close delimiter at byte offset 891seq 48:text:195, close delimiter at offset 80seq 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:7838followed byreasoning:574. - There are parts that consist of nothing but the closing delimiter:
seq 92andseq 174are 9-bytetextparts 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
textpart:<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 typedtextrather thanreasoning. - The open delimiter (hex
3c 74 68 69 6e 6b 3e) never appears on the assistant side; only the close delimiter (hex3c 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
textwith 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[]).
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.