Repository navigation
Description
Summary
A session.idle handler in one plugin instance dies with TypeError: The database connection is not open because another instance's dispose closes the process-wide connection pool. Reproduced twice on Windows — 2026-10-24 (2.33.x) and 2026-10-27 (2.34.0):
[ERROR] Idle processing error: {"error":"TypeError: The database connection is not open"}
It self-heals on the next idle (no data loss), but the profile-learning round is silently dropped and it shows up as a red error on the stats page.
Details
Log excerpt (2.34.0, 2026-10-27)
09:52:10 savePrompt -> session ses_...KrAsL
09:53:12 opencodeProvider takes precedence over memoryModel for auto-capture
09:53:15.981 Auto-capture skipped: {... "type":"skip"}
09:53:16.063 user-profile-learning: aborted: {"error":"TypeError: The database connection is not open"}
09:53:16.122 Idle processing error: {"error":"TypeError: The database connection is not open"}
09:54:12 user-profile-learning: check: {"count":14,"threshold":20} <-- next idle, recovered
The 2026-10-24 00:02:55 occurrence had the same shape (skip -> aborted -> Idle processing error), followed 0.8 s later by a fresh plugin instance init. In both cases a session had just ended in the same opencode-cli process (several sessions are hosted in one process; OpenCode host fetch unavailable / opencode providers connected bursts mark instance init).
Root cause
- Idle timer: after
performAutoCapture()it re-checkscleanedUp(dist/index.js:1044) and callsperformUserProfileLearning()(dist/index.js:1046). - Any instance's dispose ->
cleanupPlugin()->memoryClient.close()(dist/index.js:490) ->closeTursoAndInvalidateCaches()(dist/services/client.js:85) ->tursoConnectionManager.closeAll()(dist/services/turso/connection-manager.js:179).closeAll()closes every connection in the process, not just the disposing instance's. - Managers cache the raw handle:
UserPromptManagerstoresthis.dbfromgetConnection()(dist/services/user-prompt/user-prompt-manager.js:24) and reuses it inready()(lines 36-45). IfcloseAll()lands betweenready()returning the handle and the query,prepare()throws at@tursodatabase/database-common/dist/compat.js:118/promise.js:216(if (!this.open) throw TypeError("The database connection is not open")). The failing frame iscountUnanalyzedForUserLearning()->db.get(user-prompt-manager.js:157). - The
cleanedUpflag cannot prevent this: the dispose belongs to a different session's instance, so the running idle callback never observes it. - Recovery is incidental:
closeTursoAndInvalidateCaches()also resets the managers (dist/services/turso/lifecycle.js:8-16), so the next call re-opens lazily — which is why the following idle works.
Why the #412 fix does not cover this path
#412 fixed SkillsManager.getDb() by revalidating the cached handle against tursoConnectionManager (if (current === this.db) -> replace). UserPromptManager.ready(), UserProfileManager and AiSessionManager still trust this.db blindly, so the same stale-handle window is open for the idle/learning path.
Suggested fixes (either one closes the hole)
- Apply the SkillsManager keeps a stale Turso handle after boot-time closeConnection -> 'The database connection is not open' in registerDbSkills #412 pattern to every manager that caches a
TursoDbhandle, not onlySkillsManager. - And/or stop
cleanupPlugin()from calling the process-widecloseAll()while other instances are alive (reference-count plugin instances; close only on the last dispose).
Minimal repro (in-process, faithful to the race)
const db = await userPromptManager.ready(); // cached handle, as in the idle path
await closeTursoAndInvalidateCaches(); // what another instance's dispose does
await db.get('SELECT 1'); // TypeError: The database connection is not openReal-world repro: open several sessions in one Desktop process, then close one while another one's idle handler is running capture/learning. Timing window is narrow (~ms), which matches the observed frequency (twice in 4 days here; earlier occurrences were roughly monthly).
Impact
- Low severity: no corruption, no data loss.
PRAGMA integrity_checkok on all DBs/shards,profile_learning_lockempty, the next idle recovers. - Cost: that round's profile learning is skipped, and users see
Idle processing errorcounted on the stats page.
Environment
- opencode-mem version: 2.34.0 (also observed on 2.33.x on 2026-10-24)
- OpenCode version: Desktop 2.1.4 (host
opencode-cli1.4.4) - OS: Windows 11 (x64); many sessions sharing one
opencode-cliprocess
Activity
fix(turso): stop dispose closeAll from racing sibling idle handlers (#…
test(client): keep the embedding model loaded while another instance …