{"record":{"id":"997ae735b341ba75","repo":"thedotmack/claude-mem","slug":"storeobservations-on-conflict-without-existing-row-for","errorCode":null,"errorMessage":"storeObservations: ON CONFLICT without existing row for content_hash=${contentHash}","messagePattern":"storeObservations: ON CONFLICT without existing row for content_hash=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sqlite/SessionStore.ts","lineNumber":3194,"sourceCode":"          promptNumber || null,\n          discoveryTokens,\n          observation.agent_type ?? null,\n          observation.agent_id ?? null,\n          contentHash,\n          timestampIso,\n          timestampEpoch,\n          generatedByModel || null,\n          observation.metadata ?? null\n        ) as { id: number } | null;\n\n        if (inserted) {\n          observationIds.push(inserted.id);\n          continue;\n        }\n\n        const existing = lookupExistingStmt.get(memorySessionId, contentHash) as { id: number } | null;\n        if (!existing) {\n          throw new Error(\n            `storeObservations: ON CONFLICT without existing row for content_hash=${contentHash}`\n          );\n        }\n        observationIds.push(existing.id);\n      }\n\n      let summaryId: number | null = null;\n      if (summary) {\n        const rolledUp = rollupObservationFileLists(observations);\n        const filesRead = summary.files_read ?? rolledUp.files_read;\n        const filesEdited = summary.files_edited ?? rolledUp.files_edited;\n        const summaryStmt = this.db.prepare(`\n          INSERT INTO session_summaries\n          (memory_session_id, project, request, investigated, learned, completed,\n           next_steps, files_read, files_edited, notes, prompt_number, discovery_tokens, created_at, created_at_epoch)\n          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n        `);\n","sourceCodeStart":3176,"sourceCodeEnd":3212,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sqlite/SessionStore.ts#L3176-L3212","documentation":"storeObservations performs an INSERT ... ON CONFLICT(content_hash) DO UPDATE/DO NOTHING style upsert and then, for rows that conflicted, re-looks-up the existing row by (memorySessionId, contentHash) to collect its id. If the conflict path was taken but the lookup finds no row, an internal invariant of the upsert is broken, so it throws.","triggerScenarios":"Concurrent writers inserting the same content_hash into the same memory session between the conflicting INSERT and the lookupExistingStmt.get() re-read, or a deleted row / wrong session id making the lookup miss, or a manually corrupted dedupe index.","commonSituations":"Two processes (e.g. two Claude-mem workers or a CLI plus the plugin) writing identical observations simultaneously; a UNIQUE index that was changed or dropped by a migration; passing a memorySessionId whose rows were pruned mid-write.","solutions":["Retry the whole storeObservations call; the transient race usually resolves on a second attempt.","Serialize writes with a single writer connection/busy_timeout so concurrent upserts cannot interleave.","Verify the (memory_session_id, content_hash) unique index still exists and matches the ON CONFLICT clause.","Check for code that deletes observation rows concurrently (pruning/cleanup jobs) and exclude the session being written."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  ids = store.storeObservations(sessionId, project, observations);\n} catch (e) {\n  if (String(e.message).includes('ON CONFLICT without existing row')) {\n    ids = retryWithBackoff(() => store.storeObservations(sessionId, project, observations));\n  } else throw e;\n}","preventionTips":["Use a single writer process/connection for observation writes per database.","Enable SQLite busy_timeout and WAL mode to reduce concurrent-write races.","Never mutate the (memory_session_id, content_hash) unique index without migrating the ON CONFLICT clause."],"tags":["sqlite","concurrency","invariant"],"backgroundTag":"internal-invariant-violation","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"}