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: opencode zen (big-pickle) inline </think> leaks into text content instead of a reasoning part #54599
On OpenCode 2.0.25, using opencode/big-pickle via OpenCode Zen, the model's
chain-of-thought intermittently arrives inline in the assistant content
(sometimes terminated with a literal </think>). OpenCode stores the whole
string as a single text part, so the TUI renders the reasoning as ordinary
assistant output with no collapsible thinking block.
This is the V2 report of #43770 (closed as not planned because it was filed
against OpenCode 1). A comment there on Oct 11, 2026 reports the same symptom
with "opencode zen (big pickle)".
Active plugins: local plugins usage and fallback (both trivial; unrelated — the behavior predates them)
Reproduction
In OpenCode 2.0.25, select opencode/big-pickle (OpenCode Zen) as the model.
Run a multi-step agentic task that makes tool calls.
Inspect assistant messages over the session. Intermittently, a message's content contains the model's reasoning monologue as a plain text part —
often ending with a literal </think> immediately followed by the actual
answer.
Expected Behavior
Inline reasoning (including ... delimiters) should be extracted into a
separate reasoning part and rendered as a collapsible thinking block, the same
way reasoning_content is handled.
Actual Behavior
The raw reasoning text (sometimes with a literal </think>) is stored verbatim
as a single text part in session_message and displayed as normal text.
Example of one such text part:
... need to do (3 steps: get keys, connect, use).
- The honest caveat about no auto-failover.
Let me include the verification evidence.
Done.</think>Done — Groq, Cerebras, and OpenRouter are wired in. Here's the state.
## What changed
...
So a single text part contains [reasoning] </think> [final answer], and the
message reports no reasoning part at all.
Additional Context
Measured across all assistant messages in ~/.local/share/opencode/opencode.db
(parsing session_message.data, counting text parts that contain a real </think> not wrapped in backticks):
model
text parts
containing </think>
ending with </think>
opencode/big-pickle (Zen)
152
104
23
opencode/space-bunny-free (Zen)
24
0
0
lmstudio/qwen3.6-35b-a3b-mlx (openai-compatible)
84
0
0
Notes:
Only the Zen big-pickle model shows this; the other models populate reasoning_content correctly and produce proper reasoning parts.
big-picklealso produces proper reasoning parts on other turns
(62 of them), so the leak is intermittent per turn.
session.thinking: "hide" does not hide the leaked text, because it is a text part, not a reasoning part.
interleaved: { field: "reasoning_content" } is already set for big-pickle
in the catalog, so this looks like an unhandled inline-<think> case rather
than a missing mapping.
Also reproduced on a second machine running Windows with the same model/provider.
Claude models on the same setup are unaffected (their reasoning uses the proper
thinking channel), which points at inline reasoning in content specifically.
Suggested fix: run the AI SDK extractReasoningMiddleware({ tagName: "think" })
for the Zen big-pickle model (as is done for DeepSeek R1), or expose a
per-model option such as reasoning.extract: "think-tags".
Thanks for the detailed report and the database numbers. I was able to reproduce this.
There are no real Zen credentials here, so I used a simulated OpenAI-compatible model in the TUI:
Turn 1: reasoning sent on the proper reasoning channel shows up as a collapsible Thought block, as expected.
Turn 2: reasoning sent inline in content, ending with a literal </think> and no opening tag, followed by the answer. OpenCode shows all of it, </think> included, as normal answer text, with no thinking block.
It happens on 2.0.25 and still happens on the latest dev (38454c6). As far as I can tell, v2 only treats separate reasoning fields like reasoning_content as reasoning. Inline <think>/</think> tags in content are never split out, so this fits with big-pickle only sometimes sending its thinking inline.
The recording and screenshots below show it, and the script below reproduces it.
Related: #54298 (same model, reasoning saved as normal text in long sessions, likely the same cause), #54586 and #43770 (the same symptom on v1).
recording.mp4
Reproduction script (drive.ts)
// Reproduces #54599: reasoning streamed inline in the assistant `content`// (ending with a literal </think>, as opencode/big-pickle sometimes does)// is stored and rendered as plain answer text instead of a reasoning block.import{Effect}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tui: {recording: true},project: {git: true,files: {"README.md": "# demo\n"}},},({ ui, llm })=>Effect.gen(function*(){// Turn 1: reasoning on the proper reasoning channel -> collapsible "Thought" blockyield*llm.queue(Llm.reasoning("The user wants a status update. I should summarise briefly."),Llm.text("PROPER-ANSWER: reasoning above came via the reasoning channel."),Llm.finish(),)yield*ui.submit("Give me a status update")yield*ui.waitFor("PROPER-ANSWER")yield*ui.screenshot("turn1-proper-reasoning")// Turn 2: reasoning inline in content, terminated by </think> (no opening tag)yield*llm.queue(Llm.text("INLINE-REASONING: I need to list the 3 steps: get keys, connect, use.\n"+"Let me include the verification evidence.\n\nDone.</think>Done — Groq, Cerebras, and OpenRouter are wired in.",),Llm.finish(),)yield*ui.submit("Wire in the providers")yield*ui.waitFor("OpenRouter are wired in")yield*Effect.sleep("1 second")yield*ui.screenshot("turn2-inline-think-leak")}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
It splits the inline chain-of-thought (everything before the trailing response marker) out of the leaked text part into a proper reasoning part at the projection boundary (session.text.ended in message-updater.ts), so it renders as a collapsible thinking block with the answer ordered after it. Text with no marker — or a marker with nothing before it — is left untouched.
Added a focused unit test (packages/core/test/session-message-updater.test.ts). Happy to iterate on approach/location if maintainers prefer a stream-time reroute instead.
Confirmed against 2.0.26 (CLI/TUI, Windows 11, Windows Terminal), same model opencode/big-pickle#default via the opencode provider.
In a single long session the behavior flips partway through:
Assistant messages 2-119: chain-of-thought stored as a proper reasoning part (state.reasoningField: "reasoning_content"), rendered as a collapsible Thought block.
From message 120 onward: no reasoning part is created. The CoT arrives inline in a text part, terminated by a literal response, followed by the real answer.
Concrete example from opencode session export: an assistant text part of 17,737 characters containing only CoT, ending with ...Let me apply the edit. responseI found the actual cause.
Session-wide counts: response appears 105 times in assistant text parts; real opening thinking tags: 0.
The flip happens without any service restart or model change (same model/route throughout, continuous stream). The same pattern was also observed in a second session on the same model.
As noted in the report, session.thinking: "hide" / mini.thinking: "hide" have no effect here because the content sits in text, not a reasoning part, so there is no client-side workaround.
So the closing tag is the only delimiter present — the opening tag is never emitted at all.
The existing tag extraction can't catch this for two independent reasons:
It requires the opening marker. The streamed extractor only triggers on content.startsWith(" thinking"), and the batch splitter searches indexOf(" thinking"). With no opening marker, everything stays text.
It's gated to one hardcoded model id. Tag-based splitting only runs when function UVo(e){return ["qwen3-4b"].includes(e)} is true — i.e. only for qwen3-4b, not for other models that emit the same inline format.
A fix probably needs to (a) treat a lone closing </think> as "everything before it is reasoning", and/or (b) drop the model-id hardcoding. Happy to test once #54612 lands.
Adding the per-version numbers for the residual v2 shape here, as the triage on #56104 suggested.
Environment: opencode CLI 2.0.29 → 2.0.31 (npm install), Windows, model opencode/big-pickle, the same machine and the same opencode.db for all rows. Counted on assistant messages in session_message (V2 schema), grouped by the version recorded in session_v2.version:
opencode
assistant msgs
text part contains the closing marker
msgs that have a reasoning part
2.0.29
720
437 (60.7%)
92
2.0.30
291
74 (25.4%)
241
2.0.31
117
24 (20.5%)
50
The 2.0.31 numbers are not only the "marker at the start of the reply" shape: for the affected messages there is no reasoning part at all — the whole thinking trace is persisted as an ordinary text part and simply ends with the literal marker.
Real example from a session created after the 2.0.31 upgrade (20 assistant messages, 17 affected). Message content:
[
{ "type": "text", "text": "Let me find where ai-transcribe skill lives. Probably in a skill dir ... Let's search.</think>" },
{ "type": "tool", ... },
{ "type": "tool", ... }
]
No reasoning part, and the text part is pure chain-of-thought — that turn had no visible answer at all (the model called tools right after thinking). Since it is a normal text part, it is also replayed as assistant content in the next request, so the model sees its own thinking as a regular answer.
So the residual 2.0.31 behaviour looks like the same root cause as in the issue: the inline reasoning boundary is only handled when the marker is at the start of the visible text, and not when the trace occupies the whole text part. If PR #54612 splits inline reasoning in the stream parser, it would be worth checking the split is applied to the entire text part (and not only to a leading marker).
Numbers per version and shapes above are reproducible by reading the local session DB (session_v2, session_message) — happy to provide the exact queries if useful.
Em sessões longas, a resposta chega do servidor já com o raciocínio embutido na parte text. Não existe parte type: "reasoning" separada para essas mensagens. O reasoning_content vem null e frequentemente não há tag ````think` de abertura.
Medições (banco local opencode.db, 238 sessões do big-pickle)
Schema: message, part (data JSON com type), session_message (type=assistant, data.content[]), session_v2 (model).
Part types (todas as sessões): tool 28106 · step-start 26728 · step-finish 26078 · text 18894 · reasoning 14335 · patch 735 · compaction 248.
measure
value
assistant messages (big-pickle)
21,181
start with planning text
2,549 (12%)
reasoning parts present
10,137
Correlação que isola o sintoma
day
msgs with reasoning part
msgs without
hard leak with reasoning
hard leak without
2026-10-10
208
108
0
6
2026-10-11
280
170
0
3
2026-10-12
165
63
0
6
2026-10-15
8
634
0
21
2026-10-16
77
85
0
4
2026-10-17
3
31
0
2
hard leak with reasoning = 0 em todos os dias: quando a parte reasoning existe, o text está limpo; quando não existe, o planejamento aparece dentro do text. Em 2026-10-15 a relação inverte (8 com vs 634 sem).
Workarounds testados localmente
Regra de prompt rígida ("nunca emita raciocínio no corpo visível") — reduz, não elimina.
Plugin local que detecta marcadores de planejamento na parte text e injeta aviso no system prompt — funciona como guarda pós-hoc, mas o v2 não tem hook para reescrever o texto visível (experimental.text.complete não existe mais), então o turno ofensivo já é enviado.
Conclusão
O raciocínio já chega do servidor embutido no content; não há configuração ou opção de provedor que se altere do lado do cliente. A correção proposta no #54612 (dividir o chain-of-thought embutido em uma parte reasoning no lado do cliente) é o caminho.
Disponível para capturar fluxo bruto do provedor de uma rodada afetada, se for útil.
Still noticing this in 2.0.32 (also saw it on 1.18.35 before I upgraded) - here are my Big Pickle findings (Big Pickle analyzing itself... this is starting to sound bad 😅)
opencode/big-pickle, agent build. Byte-confirmed marker 3c 2f 74 68 69 6e 6b 3e (</think>); one turn's content[0] is exactly the 8-char marker, another is [CoT] + marker + [answer] with no reasoning part. Also observe the mid-session flip (seq 5/16/29 parse as reasoning, then seq 42+ leak into text). Extends the residual-shape table past 2.0.31. No new root cause.
Adding the per-session counts from my (now-closed-as-duplicate) report #56428, as requested, with the raw sqlite3 output below.
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 (<=10 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.
Raw sqlite3 output
Queries (session ses_eb6044450ffeMO126Z5pJ5rhhK, read-only copy of ~/.local/share/opencode/opencode.db):
.mode column
.headers onSELECT
(SELECTcount(*) FROM session_message WHERE session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'AND type='assistant') AS assistant_msgs,
(SELECTcount(*) FROM session_message WHERE session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'AND type='assistant'AND instr(data, x'3c2f7468696e6b3e')>0) AS msgs_with_close,
(SELECTcount(*) FROM session_message WHERE session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'AND type='assistant'AND instr(data, x'3c7468696e6b3e')>0) AS msgs_with_open,
(SELECTcount(*) FROM session_message sm, json_each(sm.data,'$.content') j WHERE json_valid(sm.data) ANDsm.session_id='ses_eb6044450ffeMO126Z5pJ5rhhK') AS parts_total,
(SELECTcount(*) FROM session_message sm, json_each(sm.data,'$.content') j WHERE json_valid(sm.data) ANDsm.session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'ANDj.value->>'type'='reasoning') AS parts_reasoning,
(SELECTcount(*) FROM session_message sm, json_each(sm.data,'$.content') j WHERE json_valid(sm.data) ANDsm.session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'ANDj.value->>'type'='text'AND instr(j.value->>'text', x'3c2f7468696e6b3e')>0) AS text_parts_with_close,
(SELECTcount(*) FROM session_message sm, json_each(sm.data,'$.content') j WHERE json_valid(sm.data) ANDsm.session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'ANDj.value->>'type'='text'AND length(j.value->>'text')<=10AND instr(j.value->>'text', x'3c2f7468696e6b3e')>0) AS close_only_parts,
(SELECTcount(*) FROM session_message sm, json_each(sm.data,'$.content') j WHERE json_valid(sm.data) ANDsm.session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'AND substr(coalesce(j.value->>'text',''),1,7)=x'3c7468696e6b3e') AS parts_starting_with_open;
SELECT'--- per assistant message: seq | part_type:length joined ---'AS note;
SELECTsm.seqAS seq,
group_concat(json_extract(j.value,'$.type') ||':'|| coalesce(length(json_extract(j.value,'$.text')),0), ' | ') AS parts
FROM session_message sm, json_each(sm.data,'$.content') j
WHERE json_valid(sm.data) ANDsm.session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'ANDsm.type='assistant'GROUP BYsm.seqORDER BYsm.seq;
SELECT'--- leaked text parts: seq | len | close_marker_byte_offset ---'AS note;
SELECTsm.seqAS seq,
length(json_extract(j.value,'$.text')) AS len,
instr(j.value->>'text', x'3c2f7468696e6b3e') AS close_at
FROM session_message sm, json_each(sm.data,'$.content') j
WHERE json_valid(sm.data) ANDsm.session_id='ses_eb6044450ffeMO126Z5pJ5rhhK'ANDsm.type='assistant'AND json_extract(j.value,'$.type')='text'AND instr(j.value->>'text', x'3c2f7468696e6b3e')>0ORDER BYsm.seq;
SELECT'--- global across all sessions ---'AS note;
SELECT
(SELECTcount(*) FROM session_message sm, json_each(sm.data,'$.content') j WHERE json_valid(sm.data) ANDj.value->>'type'='reasoning') AS reasoning_parts,
(SELECTcount(*) FROM session_message sm, json_each(sm.data,'$.content') j WHERE json_valid(sm.data) ANDj.value->>'type'='text'AND instr(j.value->>'text', x'3c2f7468696e6b3e')>0) AS text_parts_with_close;
Activity
opencode-agent commentedon Oct 11, 2026
Thanks for the detailed report and the database numbers. I was able to reproduce this.
There are no real Zen credentials here, so I used a simulated OpenAI-compatible model in the TUI:
content, ending with a literal</think>and no opening tag, followed by the answer. OpenCode shows all of it,</think>included, as normal answer text, with no thinking block.It happens on 2.0.25 and still happens on the latest
dev(38454c6). As far as I can tell, v2 only treats separate reasoning fields likereasoning_contentas reasoning. Inline<think>/</think>tags incontentare never split out, so this fits withbig-pickleonly sometimes sending its thinking inline.The recording and screenshots below show it, and the script below reproduces it.
Related: #54298 (same model, reasoning saved as normal text in long sessions, likely the same cause), #54586 and #43770 (the same symptom on v1).
recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.00kino547 commentedon Oct 11, 2026
Hope this gets fixed soon because its annoying and its hard to ser an real output and a reasoning output because they are close
ashaker20 commentedon Oct 11, 2026
Proposed fix ready: #54612
It splits the inline chain-of-thought (everything before the trailing
responsemarker) out of the leakedtextpart into a properreasoningpart at the projection boundary (session.text.endedinmessage-updater.ts), so it renders as a collapsible thinking block with the answer ordered after it. Text with no marker — or a marker with nothing before it — is left untouched.Added a focused unit test (
packages/core/test/session-message-updater.test.ts). Happy to iterate on approach/location if maintainers prefer a stream-time reroute instead.leosper commentedon Oct 13, 2026
Confirmed against 2.0.26 (CLI/TUI, Windows 11, Windows Terminal), same model
opencode/big-pickle#defaultvia theopencodeprovider.In a single long session the behavior flips partway through:
reasoningpart (state.reasoningField: "reasoning_content"), rendered as a collapsible Thought block.reasoningpart is created. The CoT arrives inline in atextpart, terminated by a literalresponse, followed by the real answer.Concrete example from
opencode session export: an assistanttextpart of 17,737 characters containing only CoT, ending with...Let me apply the edit. responseI found the actual cause.Session-wide counts:
responseappears 105 times in assistanttextparts; real openingthinkingtags: 0.The flip happens without any service restart or model change (same model/route throughout, continuous stream). The same pattern was also observed in a second session on the same model.
As noted in the report,
session.thinking: "hide"/mini.thinking: "hide"have no effect here because the content sits intext, not areasoningpart, so there is no client-side workaround.nick-o commentedon Oct 14, 2026
Adding root-cause detail from a fresh repro (#55414, macOS, v2.0.20) that may help whoever reviews #54612.
Across all assistant messages in an active
opencode/big-picklesession:thinkingmarker: 0 occurrences</think>marker: 39 occurrences (every message)So the closing tag is the only delimiter present — the opening tag is never emitted at all.
The existing tag extraction can't catch this for two independent reasons:
content.startsWith(" thinking"), and the batch splitter searchesindexOf(" thinking"). With no opening marker, everything staystext.function UVo(e){return ["qwen3-4b"].includes(e)}is true — i.e. only forqwen3-4b, not for other models that emit the same inline format.A fix probably needs to (a) treat a lone closing
</think>as "everything before it is reasoning", and/or (b) drop the model-id hardcoding. Happy to test once #54612 lands.jjdubski commentedon Oct 14, 2026
Can confirm this happens with Big Pickle model on latest opencode version v2.0.29
Running on macOS
glucmastersten-cpu commentedon Oct 16, 2026
Adding the per-version numbers for the residual v2 shape here, as the triage on #56104 suggested.
Environment: opencode CLI 2.0.29 → 2.0.31 (npm install), Windows, model
opencode/big-pickle, the same machine and the sameopencode.dbfor all rows. Counted on assistant messages insession_message(V2 schema), grouped by the version recorded insession_v2.version:reasoningpartThe
2.0.31numbers are not only the "marker at the start of the reply" shape: for the affected messages there is noreasoningpart at all — the whole thinking trace is persisted as an ordinarytextpart and simply ends with the literal marker.Real example from a session created after the 2.0.31 upgrade (20 assistant messages, 17 affected). Message content:
No reasoning part, and the
textpart is pure chain-of-thought — that turn had no visible answer at all (the model called tools right after thinking). Since it is a normaltextpart, it is also replayed as assistant content in the next request, so the model sees its own thinking as a regular answer.So the residual 2.0.31 behaviour looks like the same root cause as in the issue: the inline reasoning boundary is only handled when the marker is at the start of the visible text, and not when the trace occupies the whole text part. If PR #54612 splits inline reasoning in the stream parser, it would be worth checking the split is applied to the entire text part (and not only to a leading marker).
Numbers per version and shapes above are reproducible by reading the local session DB (
session_v2,session_message) — happy to provide the exact queries if useful.Ramon-Az commentedon Oct 17, 2026
Comentário para colar no #54599
Confirmo o mesmo padrão que vocês descrevem no #54599, com medições extraídas do banco local do OpenCode.
Ambiente
opencode/big-pickle(variants: [], custo 0 in/out)cli.jsoncomsession.thinking: "hide"já definidoO que observo
Em sessões longas, a resposta chega do servidor já com o raciocínio embutido na parte
text. Não existe partetype: "reasoning"separada para essas mensagens. Oreasoning_contentvemnulle frequentemente não há tag ````think` de abertura.Medições (banco local
opencode.db, 238 sessões do big-pickle)Schema:
message,part(dataJSON comtype),session_message(type=assistant,data.content[]),session_v2(model).Part types (todas as sessões):
tool 28106 · step-start 26728 · step-finish 26078 · text 18894 · reasoning 14335 · patch 735 · compaction 248.Correlação que isola o sintoma
hard leak with reasoning = 0em todos os dias: quando a partereasoningexiste, otextestá limpo; quando não existe, o planejamento aparece dentro dotext. Em 2026-10-15 a relação inverte (8 com vs 634 sem).Workarounds testados localmente
texte injeta aviso no system prompt — funciona como guarda pós-hoc, mas o v2 não tem hook para reescrever o texto visível (experimental.text.completenão existe mais), então o turno ofensivo já é enviado.Conclusão
O raciocínio já chega do servidor embutido no
content; não há configuração ou opção de provedor que se altere do lado do cliente. A correção proposta no #54612 (dividir o chain-of-thought embutido em uma partereasoningno lado do cliente) é o caminho.Disponível para capturar fluxo bruto do provedor de uma rodada afetada, se for útil.
--
Autor: Ramon Azevedo
Made-of-Clay commentedon Oct 17, 2026
Still noticing this in
2.0.32(also saw it on1.18.35before I upgraded) - here are my Big Pickle findings (Big Pickle analyzing itself... this is starting to sound bad 😅)Hope that's helpful. Love this tool!
Bad0Weather commentedon Oct 17, 2026
Adding the per-session counts from my (now-closed-as-duplicate) report #56428, as requested, with the raw
sqlite3output below.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.Raw
sqlite3outputQueries (session
ses_eb6044450ffeMO126Z5pJ5rhhK, read-only copy of~/.local/share/opencode/opencode.db):Output: