Repository navigation
Description
Summary
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)".
Environment
- opencode version: 2.0.25
- OS: Darwin 27.0.0 (darwin arm64)
- Terminal: ghostty (TERM=xterm-256color, COLORTERM=truecolor)
- Shell: /bin/zsh
- Install/channel: release (standalone binary)
- Active plugins: local plugins
usageandfallback(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
contentcontains the model's reasoning monologue as a plaintextpart —
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-picklemodel shows this; the other models populate
reasoning_contentcorrectly and produce properreasoningparts. big-picklealso produces properreasoningparts 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
textpart, not areasoningpart.interleaved: { field: "reasoning_content" }is already set forbig-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 incontentspecifically.
Related issues: #43770, #34126, #36387.
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".
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: