fix: make migration 17 fully idempotent for databases in intermediate states (#481)

* fix: make migration 17 idempotent and standardize column names

Migration 17 renamed columns from sdk_session_id to memory_session_id,
but the migration wasn't fully idempotent - it could fail on databases
in intermediate states. This caused errors like:
- "no such column: sdk_session_id" (when columns already renamed)
- "table observations has no column named memory_session_id" (when not renamed)

Changes:
- Rewrite renameSessionIdColumns() to check each table individually
- Use safeRenameColumn() helper that handles all edge cases gracefully
- Deprecate migration 19 (repair migration) since 17 is now idempotent
- Update maintenance scripts to use memory_session_id column name
- Update test files to use new column names

Fixes column mismatch bug in v8.2.6+

* Merge origin/main into column-mismatch

---------

Co-authored-by: Alex Newman <thedotmack@gmail.com>
This commit is contained in:
Lindsey Catlett
2025-12-29 23:30:04 -05:00
committed by GitHub
parent 61a23a14a9
commit d9e966d8f4
11 changed files with 198 additions and 240 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11 -11
View File
@@ -12,7 +12,7 @@ import { SettingsDefaultsManager } from '../src/shared/SettingsDefaultsManager';
interface ObservationRecord { interface ObservationRecord {
id: number; id: number;
sdk_session_id: string; memory_session_id: string;
project: string; project: string;
text: string | null; text: string | null;
type: string; type: string;
@@ -31,8 +31,8 @@ interface ObservationRecord {
interface SdkSessionRecord { interface SdkSessionRecord {
id: number; id: number;
claude_session_id: string; content_session_id: string;
sdk_session_id: string; memory_session_id: string;
project: string; project: string;
user_prompt: string; user_prompt: string;
started_at: string; started_at: string;
@@ -44,7 +44,7 @@ interface SdkSessionRecord {
interface SessionSummaryRecord { interface SessionSummaryRecord {
id: number; id: number;
sdk_session_id: string; memory_session_id: string;
project: string; project: string;
request: string | null; request: string | null;
investigated: string | null; investigated: string | null;
@@ -62,7 +62,7 @@ interface SessionSummaryRecord {
interface UserPromptRecord { interface UserPromptRecord {
id: number; id: number;
claude_session_id: string; content_session_id: string;
prompt_number: number; prompt_number: number;
prompt_text: string; prompt_text: string;
created_at: string; created_at: string;
@@ -117,23 +117,23 @@ async function exportMemories(query: string, outputFile: string, project?: strin
console.log(`✅ Found ${summaries.length} session summaries`); console.log(`✅ Found ${summaries.length} session summaries`);
console.log(`✅ Found ${prompts.length} user prompts`); console.log(`✅ Found ${prompts.length} user prompts`);
// Get unique SDK session IDs from observations and summaries // Get unique memory session IDs from observations and summaries
const sdkSessionIds = new Set<string>(); const memorySessionIds = new Set<string>();
observations.forEach((o) => { observations.forEach((o) => {
if (o.sdk_session_id) sdkSessionIds.add(o.sdk_session_id); if (o.memory_session_id) memorySessionIds.add(o.memory_session_id);
}); });
summaries.forEach((s) => { summaries.forEach((s) => {
if (s.sdk_session_id) sdkSessionIds.add(s.sdk_session_id); if (s.memory_session_id) memorySessionIds.add(s.memory_session_id);
}); });
// Get SDK sessions metadata via API // Get SDK sessions metadata via API
console.log('📡 Fetching SDK sessions metadata...'); console.log('📡 Fetching SDK sessions metadata...');
let sessions: SdkSessionRecord[] = []; let sessions: SdkSessionRecord[] = [];
if (sdkSessionIds.size > 0) { if (memorySessionIds.size > 0) {
const sessionsResponse = await fetch(`${baseUrl}/api/sdk-sessions/batch`, { const sessionsResponse = await fetch(`${baseUrl}/api/sdk-sessions/batch`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sdkSessionIds: Array.from(sdkSessionIds) }) body: JSON.stringify({ sdkSessionIds: Array.from(memorySessionIds) })
}); });
if (sessionsResponse.ok) { if (sessionsResponse.ok) {
sessions = await sessionsResponse.json(); sessions = await sessionsResponse.json();
+3 -3
View File
@@ -18,7 +18,7 @@ interface CorruptedObservation {
obs_created: number; obs_created: number;
session_started: number; session_started: number;
session_completed: number | null; session_completed: number | null;
sdk_session_id: string; memory_session_id: string;
} }
function formatTimestamp(epoch: number): string { function formatTimestamp(epoch: number): string {
@@ -54,9 +54,9 @@ function main() {
o.created_at_epoch as obs_created, o.created_at_epoch as obs_created,
s.started_at_epoch as session_started, s.started_at_epoch as session_started,
s.completed_at_epoch as session_completed, s.completed_at_epoch as session_completed,
s.sdk_session_id s.memory_session_id
FROM observations o FROM observations o
JOIN sdk_sessions s ON o.sdk_session_id = s.sdk_session_id JOIN sdk_sessions s ON o.memory_session_id = s.memory_session_id
WHERE o.created_at_epoch < s.started_at_epoch -- Observation older than session WHERE o.created_at_epoch < s.started_at_epoch -- Observation older than session
OR (s.completed_at_epoch IS NOT NULL OR (s.completed_at_epoch IS NOT NULL
AND o.created_at_epoch > (s.completed_at_epoch + 3600000)) -- More than 1hr after session AND o.created_at_epoch > (s.completed_at_epoch + 3600000)) -- More than 1hr after session
+6 -6
View File
@@ -20,7 +20,7 @@ const BAD_WINDOW_END = 1766626260000; // Dec 24 20:31 PST
interface AffectedObservation { interface AffectedObservation {
id: number; id: number;
sdk_session_id: string; memory_session_id: string;
created_at_epoch: number; created_at_epoch: number;
title: string; title: string;
} }
@@ -35,7 +35,7 @@ interface ProcessedMessage {
interface SessionMapping { interface SessionMapping {
session_db_id: number; session_db_id: number;
sdk_session_id: string; memory_session_id: string;
} }
interface TimestampFix { interface TimestampFix {
@@ -75,7 +75,7 @@ function main() {
// Step 1: Find affected observations // Step 1: Find affected observations
console.log('Step 1: Finding observations created during bad window...'); console.log('Step 1: Finding observations created during bad window...');
const affectedObs = db.query<AffectedObservation, []>(` const affectedObs = db.query<AffectedObservation, []>(`
SELECT id, sdk_session_id, created_at_epoch, title SELECT id, memory_session_id, created_at_epoch, title
FROM observations FROM observations
WHERE created_at_epoch >= ${BAD_WINDOW_START} WHERE created_at_epoch >= ${BAD_WINDOW_START}
AND created_at_epoch <= ${BAD_WINDOW_END} AND created_at_epoch <= ${BAD_WINDOW_END}
@@ -111,7 +111,7 @@ function main() {
obs_title: string; obs_title: string;
obs_created: number; obs_created: number;
session_started: number; session_started: number;
sdk_session_id: string; memory_session_id: string;
} }
const obsWithSessions = db.query<ObsWithSession, []>(` const obsWithSessions = db.query<ObsWithSession, []>(`
@@ -120,9 +120,9 @@ function main() {
o.title as obs_title, o.title as obs_title,
o.created_at_epoch as obs_created, o.created_at_epoch as obs_created,
s.started_at_epoch as session_started, s.started_at_epoch as session_started,
s.sdk_session_id s.memory_session_id
FROM observations o FROM observations o
JOIN sdk_sessions s ON o.sdk_session_id = s.sdk_session_id JOIN sdk_sessions s ON o.memory_session_id = s.memory_session_id
WHERE o.created_at_epoch >= ${BAD_WINDOW_START} WHERE o.created_at_epoch >= ${BAD_WINDOW_START}
AND o.created_at_epoch <= ${BAD_WINDOW_END} AND o.created_at_epoch <= ${BAD_WINDOW_END}
AND s.started_at_epoch < ${BAD_WINDOW_START} AND s.started_at_epoch < ${BAD_WINDOW_START}
+2 -2
View File
@@ -36,7 +36,7 @@ function main() {
const dec24End = 1735113600000; // Dec 25 00:00 PST const dec24End = 1735113600000; // Dec 25 00:00 PST
const dec24Obs = db.query(` const dec24Obs = db.query(`
SELECT id, sdk_session_id, created_at_epoch, title SELECT id, memory_session_id, created_at_epoch, title
FROM observations FROM observations
WHERE created_at_epoch >= ${dec24Start} WHERE created_at_epoch >= ${dec24Start}
AND created_at_epoch < ${dec24End} AND created_at_epoch < ${dec24End}
@@ -59,7 +59,7 @@ function main() {
const dec21Start = 1734768000000; // Dec 21 00:00 PST const dec21Start = 1734768000000; // Dec 21 00:00 PST
const oldObs = db.query(` const oldObs = db.query(`
SELECT id, sdk_session_id, created_at_epoch, title SELECT id, memory_session_id, created_at_epoch, title
FROM observations FROM observations
WHERE created_at_epoch >= ${dec17Start} WHERE created_at_epoch >= ${dec17Start}
AND created_at_epoch < ${dec21Start} AND created_at_epoch < ${dec21Start}
+1 -1
View File
@@ -59,7 +59,7 @@ function main() {
pm.tool_name, pm.tool_name,
pm.created_at_epoch as msg_created, pm.created_at_epoch as msg_created,
pm.status, pm.status,
s.sdk_session_id, s.memory_session_id,
s.started_at_epoch as session_started, s.started_at_epoch as session_started,
s.project s.project
FROM pending_messages pm FROM pending_messages pm
+7 -7
View File
@@ -22,7 +22,7 @@ const ORIGINAL_WINDOW_END = 1766613600000; // Dec 23 23:59 PST
interface Observation { interface Observation {
id: number; id: number;
sdk_session_id: string; memory_session_id: string;
created_at_epoch: number; created_at_epoch: number;
created_at: string; created_at: string;
title: string; title: string;
@@ -49,7 +49,7 @@ function main() {
// Check 1: Observations still in bad window // Check 1: Observations still in bad window
console.log('Check 1: Looking for observations still in bad window (Dec 24 19:45-20:31)...'); console.log('Check 1: Looking for observations still in bad window (Dec 24 19:45-20:31)...');
const badWindowObs = db.query<Observation, []>(` const badWindowObs = db.query<Observation, []>(`
SELECT id, sdk_session_id, created_at_epoch, created_at, title SELECT id, memory_session_id, created_at_epoch, created_at, title
FROM observations FROM observations
WHERE created_at_epoch >= ${BAD_WINDOW_START} WHERE created_at_epoch >= ${BAD_WINDOW_START}
AND created_at_epoch <= ${BAD_WINDOW_END} AND created_at_epoch <= ${BAD_WINDOW_END}
@@ -63,7 +63,7 @@ function main() {
for (const obs of badWindowObs) { for (const obs of badWindowObs) {
console.log(` Observation #${obs.id}: ${obs.title || '(no title)'}`); console.log(` Observation #${obs.id}: ${obs.title || '(no title)'}`);
console.log(` Timestamp: ${formatTimestamp(obs.created_at_epoch)}`); console.log(` Timestamp: ${formatTimestamp(obs.created_at_epoch)}`);
console.log(` Session: ${obs.sdk_session_id}\n`); console.log(` Session: ${obs.memory_session_id}\n`);
} }
} }
@@ -81,19 +81,19 @@ function main() {
// Check 3: Session distribution // Check 3: Session distribution
console.log('Check 3: Session distribution of corrected observations...'); console.log('Check 3: Session distribution of corrected observations...');
const sessionDist = db.query<{ sdk_session_id: string; count: number }, []>(` const sessionDist = db.query<{ memory_session_id: string; count: number }, []>(`
SELECT sdk_session_id, COUNT(*) as count SELECT memory_session_id, COUNT(*) as count
FROM observations FROM observations
WHERE created_at_epoch >= ${ORIGINAL_WINDOW_START} WHERE created_at_epoch >= ${ORIGINAL_WINDOW_START}
AND created_at_epoch <= ${ORIGINAL_WINDOW_END} AND created_at_epoch <= ${ORIGINAL_WINDOW_END}
GROUP BY sdk_session_id GROUP BY memory_session_id
ORDER BY count DESC ORDER BY count DESC
`).all(); `).all();
if (sessionDist.length > 0) { if (sessionDist.length > 0) {
console.log(`Observations distributed across ${sessionDist.length} sessions:\n`); console.log(`Observations distributed across ${sessionDist.length} sessions:\n`);
for (const dist of sessionDist.slice(0, 10)) { for (const dist of sessionDist.slice(0, 10)) {
console.log(` ${dist.sdk_session_id}: ${dist.count} observations`); console.log(` ${dist.memory_session_id}: ${dist.count} observations`);
} }
if (sessionDist.length > 10) { if (sessionDist.length > 10) {
console.log(` ... and ${sessionDist.length - 10} more sessions`); console.log(` ... and ${sessionDist.length - 10} more sessions`);
+55 -96
View File
@@ -582,126 +582,85 @@ export class SessionStore {
* Rename session ID columns for semantic clarity (migration 17) * Rename session ID columns for semantic clarity (migration 17)
* - claude_session_id → content_session_id (user's observed session) * - claude_session_id → content_session_id (user's observed session)
* - sdk_session_id → memory_session_id (memory agent's session for resume) * - sdk_session_id → memory_session_id (memory agent's session for resume)
*
* IDEMPOTENT: Checks each table individually before renaming.
* This handles databases in any intermediate state (partial migration, fresh install, etc.)
*/ */
private renameSessionIdColumns(): void { private renameSessionIdColumns(): void {
const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(17) as SchemaVersion | undefined; const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(17) as SchemaVersion | undefined;
if (applied) return; if (applied) return;
// Check if columns are already renamed (idempotent check) logger.info('DB', 'Checking session ID columns for semantic clarity rename');
const sessionsInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[];
const hasContentSessionId = sessionsInfo.some(col => col.name === 'content_session_id');
if (hasContentSessionId) { let renamesPerformed = 0;
// Already renamed, just record migration
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(17, new Date().toISOString());
return;
}
logger.info('DB', 'Renaming session ID columns for semantic clarity'); // Helper to safely rename a column if it exists
const safeRenameColumn = (table: string, oldCol: string, newCol: string): boolean => {
try {
const tableInfo = this.db.query(`PRAGMA table_info(${table})`).all() as TableColumnInfo[];
const hasOldCol = tableInfo.some(col => col.name === oldCol);
const hasNewCol = tableInfo.some(col => col.name === newCol);
// Begin transaction for atomic rename if (hasNewCol) {
this.db.run('BEGIN TRANSACTION'); // Already renamed, nothing to do
return false;
}
try { if (hasOldCol) {
// SQLite 3.25+ supports ALTER TABLE RENAME COLUMN // SQLite 3.25+ supports ALTER TABLE RENAME COLUMN
// Rename in sdk_sessions table this.db.run(`ALTER TABLE ${table} RENAME COLUMN ${oldCol} TO ${newCol}`);
this.db.run('ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id'); logger.info('DB', `Renamed ${table}.${oldCol} to ${newCol}`);
this.db.run('ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id'); return true;
}
// Rename in pending_messages table // Neither column exists - table might not exist or has different schema
this.db.run('ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id'); logger.warn('DB', `Column ${oldCol} not found in ${table}, skipping rename`);
return false;
} catch (error: any) {
// Table might not exist yet, which is fine
logger.warn('DB', `Could not rename ${table}.${oldCol}: ${error.message}`);
return false;
}
};
// Rename in observations table // Rename in sdk_sessions table
this.db.run('ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id'); if (safeRenameColumn('sdk_sessions', 'claude_session_id', 'content_session_id')) renamesPerformed++;
if (safeRenameColumn('sdk_sessions', 'sdk_session_id', 'memory_session_id')) renamesPerformed++;
// Rename in session_summaries table // Rename in pending_messages table
this.db.run('ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id'); if (safeRenameColumn('pending_messages', 'claude_session_id', 'content_session_id')) renamesPerformed++;
// Rename in user_prompts table // Rename in observations table
this.db.run('ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id'); if (safeRenameColumn('observations', 'sdk_session_id', 'memory_session_id')) renamesPerformed++;
// Commit transaction // Rename in session_summaries table
this.db.run('COMMIT'); if (safeRenameColumn('session_summaries', 'sdk_session_id', 'memory_session_id')) renamesPerformed++;
// Record migration // Rename in user_prompts table
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(17, new Date().toISOString()); if (safeRenameColumn('user_prompts', 'claude_session_id', 'content_session_id')) renamesPerformed++;
logger.info('DB', 'Successfully renamed session ID columns'); // Record migration
} catch (error: any) { this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(17, new Date().toISOString());
// Rollback on error
this.db.run('ROLLBACK'); if (renamesPerformed > 0) {
logger.error('DB', 'Session ID column rename migration error', undefined, error); logger.info('DB', `Successfully renamed ${renamesPerformed} session ID columns`);
throw error; } else {
logger.info('DB', 'No session ID column renames needed (already up to date)');
} }
} }
/** /**
* Repair session ID column renames (migration 19) * Repair session ID column renames (migration 19)
* Migration 17 may have been recorded but failed to actually rename columns. * DEPRECATED: Migration 17 is now fully idempotent and handles all cases.
* This migration checks each table and renames if needed (idempotent). * This migration is kept for backwards compatibility but does nothing.
*/ */
private repairSessionIdColumnRename(): void { private repairSessionIdColumnRename(): void {
try { const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(19) as SchemaVersion | undefined;
const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(19) as SchemaVersion | undefined; if (applied) return;
if (applied) return;
logger.info('DB', 'Checking session ID column renames (repair migration)'); // Migration 17 now handles all column rename cases idempotently.
// Just record this migration as applied.
let repairsNeeded = false; this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(19, new Date().toISOString());
// Check and fix sdk_sessions
const sessionsInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[];
if (sessionsInfo.some(col => col.name === 'claude_session_id')) {
logger.info('DB', 'Repairing sdk_sessions columns');
this.db.run('ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id');
this.db.run('ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id');
repairsNeeded = true;
}
// Check and fix pending_messages
const pendingInfo = this.db.query('PRAGMA table_info(pending_messages)').all() as TableColumnInfo[];
if (pendingInfo.some(col => col.name === 'claude_session_id')) {
logger.info('DB', 'Repairing pending_messages columns');
this.db.run('ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id');
repairsNeeded = true;
}
// Check and fix observations
const obsInfo = this.db.query('PRAGMA table_info(observations)').all() as TableColumnInfo[];
if (obsInfo.some(col => col.name === 'sdk_session_id')) {
logger.info('DB', 'Repairing observations columns');
this.db.run('ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id');
repairsNeeded = true;
}
// Check and fix session_summaries
const summariesInfo = this.db.query('PRAGMA table_info(session_summaries)').all() as TableColumnInfo[];
if (summariesInfo.some(col => col.name === 'sdk_session_id')) {
logger.info('DB', 'Repairing session_summaries columns');
this.db.run('ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id');
repairsNeeded = true;
}
// Check and fix user_prompts
const promptsInfo = this.db.query('PRAGMA table_info(user_prompts)').all() as TableColumnInfo[];
if (promptsInfo.some(col => col.name === 'claude_session_id')) {
logger.info('DB', 'Repairing user_prompts columns');
this.db.run('ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id');
repairsNeeded = true;
}
// Record migration
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(19, new Date().toISOString());
if (repairsNeeded) {
logger.info('DB', 'Session ID column rename repairs completed');
} else {
logger.info('DB', 'No session ID column repairs needed');
}
} catch (error: any) {
logger.error('DB', 'Session ID column rename repair error', undefined, error);
throw error;
}
} }
/** /**
+3 -3
View File
@@ -37,9 +37,9 @@ describe('SessionStore', () => {
const claudeId = 'claude-sess-obs'; const claudeId = 'claude-sess-obs';
const sdkId = store.createSDKSession(claudeId, 'test-project', 'initial prompt'); const sdkId = store.createSDKSession(claudeId, 'test-project', 'initial prompt');
// Get the sdk_session_id string (createSDKSession returns number ID, need string for FK) // Get the memory_session_id string (createSDKSession returns number ID, need string for FK)
// Wait, createSDKSession inserts using sdk_session_id = claude_session_id in the current implementation // createSDKSession inserts using memory_session_id = content_session_id in the current implementation
// "VALUES (?, ?, ?, ?, ?, ?, 'active')" -> claudeSessionId, claudeSessionId, ... // "VALUES (?, ?, ?, ?, ?, ?, 'active')" -> contentSessionId, contentSessionId, ...
const obs = { const obs = {
type: 'discovery', type: 'discovery',
+16 -17
View File
@@ -7,11 +7,12 @@ describe('Refactor Validation: SQL Updates', () => {
beforeEach(() => { beforeEach(() => {
db = new Database(':memory:'); db = new Database(':memory:');
// Minimal schema for sdk_sessions based on SessionStore.ts migration004 // Minimal schema for sdk_sessions based on SessionStore.ts migration004
// Uses new column names: content_session_id and memory_session_id
db.run(` db.run(`
CREATE TABLE sdk_sessions ( CREATE TABLE sdk_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
claude_session_id TEXT UNIQUE NOT NULL, content_session_id TEXT UNIQUE NOT NULL,
sdk_session_id TEXT UNIQUE, memory_session_id TEXT UNIQUE,
project TEXT NOT NULL, project TEXT NOT NULL,
user_prompt TEXT, user_prompt TEXT,
started_at TEXT, started_at TEXT,
@@ -27,28 +28,26 @@ describe('Refactor Validation: SQL Updates', () => {
db.close(); db.close();
}); });
it('should update sdk_session_id using direct SQL (replacing updateSDKSessionId)', () => { it('should update memory_session_id using direct SQL (replacing updateSDKSessionId)', () => {
// Setup initial state: A session without an sdk_session_id // Setup initial state: A session without a memory_session_id
const claudeId = 'claude-session-123'; const contentId = 'content-session-123';
const syntheticId = 'sdk-session-456'; const memoryId = 'memory-session-456';
db.prepare(` db.prepare(`
INSERT INTO sdk_sessions (claude_session_id, project, started_at, started_at_epoch) INSERT INTO sdk_sessions (content_session_id, project, started_at, started_at_epoch)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
`).run(claudeId, 'test-project', '2025-01-01T00:00:00Z', 1735689600000); `).run(contentId, 'test-project', '2025-01-01T00:00:00Z', 1735689600000);
// Verify initial state // Verify initial state
const before = db.prepare('SELECT sdk_session_id FROM sdk_sessions WHERE claude_session_id = ?').get(claudeId) as any; const before = db.prepare('SELECT memory_session_id FROM sdk_sessions WHERE content_session_id = ?').get(contentId) as any;
expect(before.sdk_session_id).toBeNull(); expect(before.memory_session_id).toBeNull();
// EXECUTE: The exact SQL statement from the refactor in import-xml-observations.ts // EXECUTE: The exact SQL statement from the refactor
// Original code: db['db'].prepare('UPDATE sdk_sessions SET sdk_session_id = ? WHERE claude_session_id = ?').run(syntheticSdkSessionId, sessionMeta.sessionId); const stmt = db.prepare('UPDATE sdk_sessions SET memory_session_id = ? WHERE content_session_id = ?');
stmt.run(memoryId, contentId);
const stmt = db.prepare('UPDATE sdk_sessions SET sdk_session_id = ? WHERE claude_session_id = ?');
stmt.run(syntheticId, claudeId);
// VERIFY: The update happened // VERIFY: The update happened
const after = db.prepare('SELECT sdk_session_id FROM sdk_sessions WHERE claude_session_id = ?').get(claudeId) as any; const after = db.prepare('SELECT memory_session_id FROM sdk_sessions WHERE content_session_id = ?').get(contentId) as any;
expect(after.sdk_session_id).toBe(syntheticId); expect(after.memory_session_id).toBe(memoryId);
}); });
}); });