Repository navigation
Description
Description
Had a few sessions wedge themselves last night (desktop 2.0.12, still happening on 2.0.14). Every prompt in an affected session fails with Failed to drain Session, forever. New sessions are fine. Saw it 33 times in one evening across several sessions.
From ~/.local/share/opencode/log/opencode.log:
ERROR Failed to drain Session error=TypeError: s.result.value.map is not a function. (In 's.result.value.map((a) => {
if (a.type !== "file") return a;
return pm(a.mime, a.name, o) ?? a;
})', 's.result.value.map' is undefined)
at <anonymous> (chunk-bypdxy5k.js:113:1087)
...
at prepare (chunk-bypdxy5k.js:114:784)
at runStep
at drain
That minified snippet is unsupportedParts in packages/core/src/session/model-request.ts:
if (part.type !== "tool-result" || part.result.type !== "content") return part
return { ...part, result: { ...part.result, value: part.result.value.map(...) } }So somewhere a tool-result reaches prepare with result.type === "content" but a value that isn't an array, the .map throws, and the throw takes out runStep -> drain, which is why the session can never recover. boundImages a few lines below has the same assumption in two places (a .reduce and a .map).
I poked at opencode.db for one of the dead sessions first, assuming something bad got persisted, but all 70 stored tool parts look completely normal (state: {status, input, content: [...]}). So whatever produces the bad part happens in memory during request prep, not on write. Couldn't catch the actual producer, unfortunately.
Wherever it comes from, one malformed tool result shouldn't be able to brick a whole session. Opened #50776 which degrades those parts to a text placeholder in unsupportedParts/boundImages instead of throwing.
Plugins
opencode-auth, opencode-openai-codex-auth, zztkm-vdot, plus a local dev plugin (open-toolbox)
OpenCode version
Desktop app 2.0.14 (bundled CLI); first seen on 2.0.12
Steps to reproduce
- Have a session whose prepared request contains a tool-result part with
{ result: { type: "content", value: undefined } }(no reliable way to force this - it just happens sometimes mid-session, provider wasopencodezen via the openai-compatible endpoint) - Send any message in that session (or let it resume on app start)
- Every drain fails with the TypeError above; the session is permanently wedged. New sessions work fine.
Operating System
Windows 11
Terminal
n/a (desktop app)
Activity
rekram1-node commentedon Sep 23, 2026
Your stored tool parts look fine, so something is putting a non-array value on a tool result in memory right before the request, most likely a plugin hook editing event.messages. Does the session work again if you disable your plugins (especially open-toolbox), and does any of them rewrite tool results, like truncating old output into a string?
twotimemonger commentedon Sep 23, 2026
It was my plugin, I wrote a string value into a tool result. I fixed that on my end, but I kept #50776 open so no hook can permanently wedge a session: it degrades to a placeholder, keeping the contract that hooks can edit messages while core still drains. you can close if this is not the correct direction.
PestoAddict commentedon Oct 7, 2026
Different trigger, same permanent wedge — plus a recovery that works.
Environment
TERM=xterm-256colorWhat happened
A subagent session stopped producing steps mid-task: 68 min wall clock for 86 s of real model generation (14 steps). It sat in
runningfor 33 min with zero output, ran a few steps, then sat for another 18 min.Log, in the middle of the first stall:
So the trigger here is not the
TypeErrorinunsupportedParts— it is an upstream 400 rejecting a request that carried more images than the provider allows. Same outcome: after the failed drain the session is never scheduled again, nothing is surfaced to the client, and the parent agent blocks.It can take more than one session down at once. At one point
/api/session/activelisted 7 sessions inrunningwithtokens.outputfrozen./api/permission/requestand/api/session/<id>/permissionwere both empty — nothing was waiting on an approval. A wedged session produces no log line of its own, so it looks idle rather than failed.Recovery that works:
opencode reload. 2/2 wedged sessions resumed within the exact second of a config/plugin reload — the log showswatcher stopped → "loading plugin" ...at the same second the next step finally ran (16:53:14Z, 17:23:13Z). So the state is recoverable when the runner re-drains: a retry/reset on drain failure would unwedge it without a manual reload, and the failure should reach the client instead of silence (see #51113).Question: should a request exceeding the provider's image limit be bounded before send, and/or should that 400 become a per-session error rather than a wedged runner?
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I reproduced the wedge, though I didn't find what creates the malformed part in your sessions. To test the crash itself, I used a small local plugin whose session
contexthook rewrites tool results to{ type: "content", value: undefined }, then had the model callread.Failed to drain Session … TypeError: undefined is not an object (evaluating '…result.value.map'). The model is never called again, every later prompt in that session fails the same way, and the TUI just sits idle with no error shown.dev(fa1260d6). The throw comes fromreplaceMedia, whichunsupportedPartsuses, inpackages/core/src/session/model-request.ts.boundImagesmakes the same array assumption (.flatMap).The script and recording below show the steps. The script prints the
opencode.loglines with the error. So one malformed tool result can wedge a whole session, as you described. Where the bad part comes from in your setup is still open. Your plugins (including the localopen-toolbox) can rewrite messages through session hooks, so they are worth checking. Related: #55271 and #53633 (other request-preparation errors that end a session's drain), #52042 and #54325 (other sessions that get stuck with no way to recover).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.