perf(opencode): run session listing off the event loop in a worker#391
Conversation
Plan for moving OpencodeProvider.listSessionsDirect()'s synchronous node:sqlite marker scan off the shared event loop into a worker_thread. Retains the superseded 2026-06-03 DB-change-gate plan as the record of how live measurement falsified the per-session-cache and gate approaches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OpencodeProvider.listSessionsDirect() ran a ~180ms / ~432MB synchronous node:sqlite scan (the hasThreeViewsMarker LIKE over part.data+message.data) on the shared event loop every ~7s while OpenCode is active, freezing every terminal and pane under multi-agent load. Move the blocking query into a worker_thread so it never blocks the loop. - Extract the synchronous DB work into a pure query module (opencode-listing-query.ts) with a lazy node:sqlite import and per-table marker detection. - Add a sentinel-guarded worker entry (opencode-listing.worker.ts). The main thread loads it by exact sibling URL (.ts in dev/test, .js in prod) to dodge NodeNext's broken .js->.ts module remap inside workers. - Add a spawn-per-call, shape-validated, timeout-bounded worker runner (opencode-listing-runner.ts) plus an in-process runner for tests. - Provider returns [] for an absent/empty DB but THROWS on read failure, so the indexer never prunes the sidebar on a transient worker hiccup. - Indexer preserves cached direct-provider sessions on listing failure (full scan + incremental paths). Covered by unit tests (query/runner/worker) and integration tests that spawn the real worker, prove the loop stays responsive while it runs, and exercise the compiled .js worker path. The TS-worker test guard uses process.features.typescript as a capability probe instead of version math. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8f43a9f200
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| s.time_updated AS lastActivityAt, | ||
| p.worktree AS projectPath, | ||
| ${markerExpr} AS hasThreeViewsMarker | ||
| FROM session s |
There was a problem hiding this comment.
Return an empty result when the schema is not created yet
If opencode.db already exists but OpenCode has not created the session table yet (for example a freshly-created/zero-byte DB, or a reset DB before schema initialization), this query throws no such table: session. Because listSessionsDirect() now rethrows worker/query failures and the indexer preserves cached direct sessions on that path, this scenario is treated as a transient read failure instead of an empty database, leaving stale OpenCode sessions in the sidebar until a later successful scan. Guard for a missing session table and return { rows: [], schemaMissingParentId: false } before preparing this SELECT.
Useful? React with 👍 / 👎.
Problem
OpencodeProvider.listSessionsDirect()ran a ~180 ms / ~432 MB synchronousnode:sqlitescan on Freshell's shared event loop every ~7 s while OpenCode is active. The scan is thehasThreeViewsMarkerleading-wildcardLIKEoverpart.data+message.data. Because the indexer refreshes on activity in any provider (Claude/Codex too), this blocking scan fired ~12×/min and froze every terminal and pane under multi-agent load.The cache/DB-change-gate approaches were investigated first and falsified by live measurement (OpenCode's
-waladvances every refresh, so a gate would never hit). The converged fix is to run the query off the event-loop thread.Change
Move the blocking query into a
worker_thread:opencode-listing-query.ts— pure query module; lazynode:sqliteimport, per-table marker detection.opencode-listing.worker.ts— sentinel-guarded worker entry. The main thread loads it by exact sibling URL (.tsin dev/test,.jsin prod) to dodge NodeNext's broken.js→.tsremap inside workers.opencode-listing-runner.ts— spawn-per-call, shape-validated, timeout-bounded worker runner; plus an in-process runner for tests.opencode.ts— injectable query runner; returns[]for an absent/empty DB but throws on read failure so the indexer never prunes the sidebar on a transient worker hiccup.session-indexer.ts— preserves cached direct-provider sessions on listing failure (full-scan + incremental paths).Tests
.jsworker path.process.features.typescriptas a capability probe (not Node-version math), so it's correct across the Electron 22.12 runtime, Node 22.18+, and 23.x.Verification
Full coordinated suite green (client + server + electron). Validated empirically on a live 533 MB OpenCode DB: the worker returns 343 rows while the main loop keeps ticking, with no fd leak over 100 spawns.
Plan:
docs/superpowers/plans/2026-06-04-opencode-listing-offthread-worker.md(the superseded2026-06-03gate plan is retained as the measurement record).🤖 Generated with Claude Code