Skip to content

[Bug]: OpenCode provider hangs opening/resuming a large thread — readThread pulls full session history unpaginated, no timeout #3601

Description

@vdmkotai

Before submitting

  • I searched existing issues and did not find a duplicate. Closest is [Bug]: Opencode server frequently disconnects / connection lost #2579 (connection drops / idle-TTL / reconnect) — this is a different root cause (a full‑history hydration hang); cross‑referenced under "Related".
  • I included enough detail to reproduce or investigate the problem.

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

  1. Use the OpenCode provider (t3code spawns opencode serve and drives it via @opencode-ai/sdk).
  2. 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.)
  3. Close and reopen / resume that thread in t3code (triggers readThread → session.messages with no limit).
  4. 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.

Activity

  1. vdmkotai commented on Jun 29, 2026

    @vdmkotai
    ContributorAuthor

    Correction after a deeper, independent investigation.

    The symptom that originally motivated this issue — a user losing conversation context on a follow-up within the same thread — turns out to have a different root cause, now filed separately as #3604.

    In short: t3code doesn't persist/reuse the OpenCode ses_… for a thread. OpenCodeAdapter.startSession always calls client.session.create() and ignores the resume cursor; provider_session_runtime.resume_cursor_json is NULL for every OpenCode thread. After the session is reaped (~30 min idle, e.g. following a long sub-agent turn) or on restart, the next follow-up is sent to a new, empty session while the old chat is still rendered from t3code's projection DB — so it's contextless, with no hang. Direct evidence: several threads have two T3 Code <threadId> top-level sessions in OpenCode's DB (details + table in #3604).

    This issue (#3601) may still be a valid, lower-priority performance concern — the unpaginated full-history session.messages() in readThread (OpenCodeAdapter.ts:1389) — but it is not the cause of the context loss. Feel free to close it if it isn't independently useful.

  2. t3dotgg commented on Aug 27, 2026

    @t3dotgg
    Member

    Thanks for the detailed report and follow-up.

    The reporter withdrew the causal claim behind this report. The actual observed failure was context loss, not a large-history hang, and was moved to issue #3604. The durable OpenCode resume fix landed for that defect. The remaining unbounded read is real source behavior, but the reporter explicitly called it a possible lower-priority concern without an independent hang reproduction and invited closure.

    I'm closing this as obsolete as part of an automated pass on all open issues. If the original behavior has a new reproduction on a current build, please reply with the version and fresh logs, and we can reopen it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions