Before submitting
Area
apps/server
Summary
When t3code reads an existing OpenCode thread (OpenCodeAdapter.readThread), it pulls the entire session history in one client.session.messages({ sessionID }) call with no limit/cursor and no timeout/abort. For a thread whose OpenCode session has accumulated many large message parts, that single call becomes a very large SQLite read → JSON serialize → HTTP transfer → in‑memory rebuild, and opening/resuming the thread hangs ("couldn't pull the session") with no error surfaced.
This is not caused by total OpenCode DB size: the OpenCode CLI/TUI opens the same sessions fine (it reads the DB locally/incrementally), and OpenCode's HTTP API already exposes pagination that t3code isn't using.
Steps to reproduce
- Use the OpenCode provider (t3code spawns
opencode serve and drives it via @opencode-ai/sdk).
- Build up a long thread that produced large tool outputs / file dumps, so its OpenCode session accumulates tens of MB of parts. (On the affected machine: individual sessions with 13–43 MB of parts; some single
part/event rows ~12 MB.)
- Close and reopen / resume that thread in t3code (triggers
readThread → session.messages with no limit).
- The thread hangs on load (worse under concurrent sessions). The same session opens normally via the
opencode CLI/TUI.
Expected behavior
Opening/resuming a thread loads promptly, or fails fast with a clear error. Large histories are paged/streamed rather than fetched whole, and a slow load times out with a surfaced message instead of spinning indefinitely.
Actual behavior
The thread never finishes loading; the UI sits in a pending / "pulling session" state with no error. Because the hydration call has no timeout, nothing is logged as a failure — it just hangs.
Root cause (source analysis)
1. t3code fetches the full history, unpaginated, with no timeout/abort — apps/server/src/provider/Layers/OpenCodeAdapter.ts:1386-1410 (readThread):
const messages = yield* runOpenCodeSdk("session.messages", () =>
context.client.session.messages({ sessionID: context.openCodeSessionId }),
).pipe(Effect.mapError(toRequestError));
// then rebuilds every assistant turn in memory from messages.data (1396-1403)
No limit/before cursor, no AbortSignal, no timeout/retry. (rollbackThread, 1412-1434, is the same.) The only explicit timeout in the OpenCode runtime is the 5s server‑spawn readiness wait (opencodeRuntime.ts:39-41, 441-467); nothing guards session reads.
2. OpenCode returns the whole conversation when no limit is sent — packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:117-119:
if (ctx.query.limit === undefined || ctx.query.limit === 0) {
return ... session.messages({ sessionID: ctx.params.sessionID }) // full list
}
// else: MessageV2.page(...) with Link / X-Next-Cursor (121-141)
Pagination exists (limit + before cursor, Link / X-Next-Cursor headers) but is only used when a limit is supplied — which t3code never sends. Hydration also loads all parts for the page in one WHERE message_id IN (...) and groups them in memory (packages/opencode/src/session/message-v2.ts:98-123).
Why the CLI works and DB size is a red herring: the CLI/TUI reads SQLite directly/incrementally; the trigger is the weight of the specific session being opened, not total DB size (a large DB of small sessions is fine, and previously‑larger DBs worked). It's also not the event stream — t3code subscribes live (event.subscribe(undefined, { signal }), OpenCodeAdapter.ts:977-982) and the server /event SSE is live‑only with no DB replay (handlers/event.ts:25-76), so the event table size is irrelevant to hydration.
Suggested fix
- Paginate
readThread / rollbackThread using the cursor pagination OpenCode already exposes (limit + before, Link / X-Next-Cursor): load the latest page and lazily page older turns.
- Add a timeout +
AbortSignal (and a surfaced error) to the hydration calls, mirroring the server‑spawn timeout handling in opencodeRuntime.ts:462-467, so a heavy session fails fast instead of hanging.
- Optionally avoid fully materializing
messages.data in memory for very large sessions.
Impact
Major degradation or frequent failure — specific heavy threads become effectively unopenable, and it worsens over time as sessions grow.
Version or commit
T3 Code Nightly (desktop). OpenCode server 1.17.11; @opencode-ai/sdk 1.15.13.
Environment
macOS (Apple Silicon); provider = opencode (t3code‑spawned opencode serve).
Logs or stack traces
No timeout is logged (there is no timeout on the hydration path — it hangs rather than failing). The only correlated lines in OpenCode's own log (~/.local/share/opencode/log/opencode.log) are turn aborts that line up with cancelling the stuck load:
level=ERROR ... message=process session.id=ses_… messageID=msg_… error=Aborted stack=undefined
Workaround
Start a new thread instead of resuming a heavy one. Removing/trimming the heaviest sessions also avoids it — but only because that removes the heavy session, not because total DB size matters.
Related
#2579 (OpenCode server disconnects / connection lost) — overlaps on "no clear error in UI" and the 5s startup timeout, but is a different root cause (reconnect / idle‑TTL vs. unpaginated hydration). Filed separately to keep one problem per issue.
Before submitting
Area
apps/server
Summary
When t3code reads an existing OpenCode thread (
OpenCodeAdapter.readThread), it pulls the entire session history in oneclient.session.messages({ sessionID })call with nolimit/cursor and no timeout/abort. For a thread whose OpenCode session has accumulated many large message parts, that single call becomes a very large SQLite read → JSON serialize → HTTP transfer → in‑memory rebuild, and opening/resuming the thread hangs ("couldn't pull the session") with no error surfaced.This is not caused by total OpenCode DB size: the OpenCode CLI/TUI opens the same sessions fine (it reads the DB locally/incrementally), and OpenCode's HTTP API already exposes pagination that t3code isn't using.
Steps to reproduce
opencode serveand drives it via@opencode-ai/sdk).part/eventrows ~12 MB.)readThread→session.messageswith nolimit).opencodeCLI/TUI.Expected behavior
Opening/resuming a thread loads promptly, or fails fast with a clear error. Large histories are paged/streamed rather than fetched whole, and a slow load times out with a surfaced message instead of spinning indefinitely.
Actual behavior
The thread never finishes loading; the UI sits in a pending / "pulling session" state with no error. Because the hydration call has no timeout, nothing is logged as a failure — it just hangs.
Root cause (source analysis)
1. t3code fetches the full history, unpaginated, with no timeout/abort —
apps/server/src/provider/Layers/OpenCodeAdapter.ts:1386-1410(readThread):No
limit/beforecursor, noAbortSignal, no timeout/retry. (rollbackThread, 1412-1434, is the same.) The only explicit timeout in the OpenCode runtime is the 5s server‑spawn readiness wait (opencodeRuntime.ts:39-41,441-467); nothing guards session reads.2. OpenCode returns the whole conversation when no limit is sent —
packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:117-119:Pagination exists (
limit+beforecursor,Link/X-Next-Cursorheaders) but is only used when alimitis supplied — which t3code never sends. Hydration also loads all parts for the page in oneWHERE message_id IN (...)and groups them in memory (packages/opencode/src/session/message-v2.ts:98-123).Why the CLI works and DB size is a red herring: the CLI/TUI reads SQLite directly/incrementally; the trigger is the weight of the specific session being opened, not total DB size (a large DB of small sessions is fine, and previously‑larger DBs worked). It's also not the event stream — t3code subscribes live (
event.subscribe(undefined, { signal }),OpenCodeAdapter.ts:977-982) and the server/eventSSE is live‑only with no DB replay (handlers/event.ts:25-76), so theeventtable size is irrelevant to hydration.Suggested fix
readThread/rollbackThreadusing the cursor pagination OpenCode already exposes (limit+before,Link/X-Next-Cursor): load the latest page and lazily page older turns.AbortSignal(and a surfaced error) to the hydration calls, mirroring the server‑spawn timeout handling inopencodeRuntime.ts:462-467, so a heavy session fails fast instead of hanging.messages.datain memory for very large sessions.Impact
Major degradation or frequent failure — specific heavy threads become effectively unopenable, and it worsens over time as sessions grow.
Version or commit
T3 Code Nightly (desktop). OpenCode server
1.17.11;@opencode-ai/sdk1.15.13.Environment
macOS (Apple Silicon); provider = opencode (t3code‑spawned
opencode serve).Logs or stack traces
No timeout is logged (there is no timeout on the hydration path — it hangs rather than failing). The only correlated lines in OpenCode's own log (
~/.local/share/opencode/log/opencode.log) are turn aborts that line up with cancelling the stuck load:Workaround
Start a new thread instead of resuming a heavy one. Removing/trimming the heaviest sessions also avoids it — but only because that removes the heavy session, not because total DB size matters.
Related
#2579 (OpenCode server disconnects / connection lost) — overlaps on "no clear error in UI" and the 5s startup timeout, but is a different root cause (reconnect / idle‑TTL vs. unpaginated hydration). Filed separately to keep one problem per issue.