{"record":{"id":"ed8d1c96bc1df4d2","repo":"thedotmack/claude-mem","slug":"session-sessiondbid-not-found-in-sdk-sessions","errorCode":null,"errorMessage":"Session ${sessionDbId} not found in sdk_sessions","messagePattern":"Session (.+?) not found in sdk_sessions","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sqlite/SessionStore.ts","lineNumber":2291,"sourceCode":"    const nowIso = new Date(nowEpoch).toISOString();\n    this.db.prepare(`\n      UPDATE sdk_sessions\n      SET status = 'completed', completed_at = ?, completed_at_epoch = ?\n      WHERE id = ?\n    `).run(nowIso, nowEpoch, sessionDbId);\n  }\n\n  ensureMemorySessionIdRegistered(\n    sessionDbId: number,\n    memorySessionId: string,\n    workerPort?: number\n  ): string {\n    const session = this.db.prepare(`\n      SELECT id, memory_session_id, worker_port FROM sdk_sessions WHERE id = ?\n    `).get(sessionDbId) as { id: number; memory_session_id: string | null; worker_port: number | null } | undefined;\n\n    if (!session) {\n      throw new Error(`Session ${sessionDbId} not found in sdk_sessions`);\n    }\n\n    // REGISTER, DO NOT RE-REGISTER. `memory_session_id` is the FK parent key of\n    // `observations` and `session_summaries` (ON UPDATE CASCADE) and the join\n    // field `requeuePromptSync` pushes to replicas, so overwriting it is not a\n    // field update — it rewrites every memory the session owns and re-enqueues\n    // every prompt it has.\n    //\n    // The caller that made this matter is ClaudeProvider: a fresh SDK process\n    // mints a new session_id every turn, `resetCarriedMemorySessionId` clears the\n    // in-memory copy before each one, and nothing consumes a later turn's id\n    // (`shouldResume` is a hardcoded false, so `resume` never receives it). The\n    // condition below used to be `!==`, so every turn looked like a new identity.\n    //\n    // MEASURED on one store: sync_outbox held 1,100,783 rows for 6,930 distinct\n    // prompts — 158.8x, 393 MB of an 854 MB database — with its worst single\n    // prompt carrying 3,464 rows and 3,464 DISTINCT memory_session_ids. That is\n    // `requeuePromptSync`, whose own docstring describes a one-time repair","sourceCodeStart":2273,"sourceCodeEnd":2309,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sqlite/SessionStore.ts#L2273-L2309","documentation":"SessionStore looks up a session by its numeric database id in sdk_sessions to obtain memory_session_id and worker_port. When no row matches, it throws 'Session <id> not found in sdk_sessions', indicating the caller referenced a session that does not exist (deleted, wrong id, or never registered).","triggerScenarios":"Calling SessionStore methods (e.g. the public method at src/services/sqlite/SessionStore.ts:2291) with a sessionDbId that is not present in sdk_sessions — a stale/hard-deleted session id, a typo'd id, or querying before the session was registered.","commonSituations":"Workers or hooks retaining a session id across a database reset; resuming from persisted state whose session was pruned; passing a Claude/session UUID instead of the numeric DB id.","solutions":["Verify the id exists: SELECT id, memory_session_id FROM sdk_sessions WHERE id = ?","Ensure you pass the numeric DB id (sdk_sessions.id), not a memory/session UUID","Re-register the session if it was deleted before retrying the operation","Handle the throw: treat a missing session as 'recreate from source' in worker code"],"exampleFix":"// before\nstore.getWorkerPort(staleSessionId); // throws\n// after\nconst exists = store.findSession(staleSessionId);\nif (!exists) session = store.registerSession(newSessionId);","handlingStrategy":"try-catch","validationCode":"const row = db.prepare('SELECT id FROM sdk_sessions WHERE id = ?').get(sessionDbId);\nif (!row) throw new Error(`Skip: session ${sessionDbId} no longer exists`);","typeGuard":"function sessionExists(db: Database, sessionDbId: number): boolean {\n  return !!db.prepare('SELECT 1 FROM sdk_sessions WHERE id = ?').get(sessionDbId);\n}","tryCatchPattern":"try { info = store.getSessionWorkerInfo(sessionDbId); } catch (e) {\n  if (String(e.message).startsWith('Session ') && String(e.message).endsWith('not found in sdk_sessions')) {\n    info = registerNewSession(); // stale id — recreate\n  } else throw e;\n}","preventionTips":["Persist the numeric sdk_sessions.id, not a UUID, in worker state","Purge worker/job state when the database is reset","Check session existence before enqueuing background work for it"],"tags":["sqlite","session","not-found"],"backgroundTag":"record-not-found","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}