#!/usr/bin/env bun /** * Fix Corrupted Observation Timestamps * * This script repairs observations that were created during the orphan queue processing * on Dec 24, 2025 between 19:45-20:31. These observations got Dec 24 timestamps instead * of their original timestamps from Dec 17-20. */ import Database from 'bun:sqlite'; import { resolve } from 'path'; const DB_PATH = resolve(process.env.HOME!, '.claude-mem/claude-mem.db'); // Bad window: Dec 24 19:45-20:31 (timestamps in milliseconds, not microseconds) // Using actual observation epoch format (microseconds since epoch) const BAD_WINDOW_START = 1766623500000; // Dec 24 19:45 PST const BAD_WINDOW_END = 1766626260000; // Dec 24 20:31 PST interface AffectedObservation { id: number; memory_session_id: string; created_at_epoch: number; title: string; } interface ProcessedMessage { id: number; session_db_id: number; tool_name: string; created_at_epoch: number; completed_at_epoch: number; } interface SessionMapping { session_db_id: number; memory_session_id: string; } interface TimestampFix { observation_id: number; observation_title: string; wrong_timestamp: number; correct_timestamp: number; session_db_id: number; pending_message_id: number; } function formatTimestamp(epoch: number): string { return new Date(epoch).toLocaleString('en-US', { timeZone: 'America/Los_Angeles', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } function main() { const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); const autoYes = args.includes('--yes') || args.includes('-y'); console.log('šŸ” Analyzing corrupted observation timestamps...\n'); if (dryRun) { console.log('šŸƒ DRY RUN MODE - No changes will be made\n'); } const db = new Database(DB_PATH); try { // Step 1: Find affected observations console.log('Step 1: Finding observations created during bad window...'); const affectedObs = db.query(` SELECT id, memory_session_id, created_at_epoch, title FROM observations WHERE created_at_epoch >= ${BAD_WINDOW_START} AND created_at_epoch <= ${BAD_WINDOW_END} ORDER BY id `).all(); console.log(`Found ${affectedObs.length} observations in bad window\n`); if (affectedObs.length === 0) { console.log('āœ… No affected observations found!'); return; } // Step 2: Find processed pending_messages from bad window console.log('Step 2: Finding pending messages processed during bad window...'); const processedMessages = db.query(` SELECT id, session_db_id, tool_name, created_at_epoch, completed_at_epoch FROM pending_messages WHERE status = 'processed' AND completed_at_epoch >= ${BAD_WINDOW_START} AND completed_at_epoch <= ${BAD_WINDOW_END} ORDER BY completed_at_epoch `).all(); console.log(`Found ${processedMessages.length} processed messages\n`); // Step 3: Match observations to their session start times (simpler approach) console.log('Step 3: Matching observations to session start times...'); const fixes: TimestampFix[] = []; interface ObsWithSession { obs_id: number; obs_title: string; obs_created: number; session_started: number; memory_session_id: string; } const obsWithSessions = db.query(` SELECT o.id as obs_id, o.title as obs_title, o.created_at_epoch as obs_created, s.started_at_epoch as session_started, s.memory_session_id FROM observations o JOIN sdk_sessions s ON o.memory_session_id = s.memory_session_id WHERE o.created_at_epoch >= ${BAD_WINDOW_START} AND o.created_at_epoch <= ${BAD_WINDOW_END} AND s.started_at_epoch < ${BAD_WINDOW_START} ORDER BY o.id `).all(); for (const row of obsWithSessions) { fixes.push({ observation_id: row.obs_id, observation_title: row.obs_title || '(no title)', wrong_timestamp: row.obs_created, correct_timestamp: row.session_started, session_db_id: 0, // Not needed for this approach pending_message_id: 0 // Not needed for this approach }); } console.log(`Identified ${fixes.length} observations to fix\n`); // Step 5: Display what will be fixed console.log('═══════════════════════════════════════════════════════════════════════'); console.log('PROPOSED FIXES:'); console.log('═══════════════════════════════════════════════════════════════════════\n'); for (const fix of fixes) { const daysDiff = Math.round((fix.wrong_timestamp - fix.correct_timestamp) / (1000 * 60 * 60 * 24)); console.log(`Observation #${fix.observation_id}: ${fix.observation_title}`); console.log(` āŒ Wrong: ${formatTimestamp(fix.wrong_timestamp)}`); console.log(` āœ… Correct: ${formatTimestamp(fix.correct_timestamp)}`); console.log(` šŸ“… Off by ${daysDiff} days\n`); } // Step 6: Ask for confirmation console.log('═══════════════════════════════════════════════════════════════════════'); console.log(`Ready to fix ${fixes.length} observations.`); if (dryRun) { console.log('\nšŸƒ DRY RUN COMPLETE - No changes made.'); console.log('Run without --dry-run flag to apply fixes.\n'); db.close(); return; } if (autoYes) { console.log('Auto-confirming with --yes flag...\n'); applyFixes(db, fixes); return; } console.log('Apply these fixes? (y/n): '); const stdin = Bun.stdin.stream(); const reader = stdin.getReader(); reader.read().then(({ value }) => { const response = new TextDecoder().decode(value).trim().toLowerCase(); if (response === 'y' || response === 'yes') { applyFixes(db, fixes); } else { console.log('\nāŒ Fixes cancelled. No changes made.'); db.close(); } }); } catch (error) { console.error('āŒ Error:', error); db.close(); process.exit(1); } } function applyFixes(db: Database, fixes: TimestampFix[]) { console.log('\nšŸ”§ Applying fixes...\n'); const updateStmt = db.prepare(` UPDATE observations SET created_at_epoch = ?, created_at = datetime(?/1000, 'unixepoch') WHERE id = ? `); let successCount = 0; let errorCount = 0; for (const fix of fixes) { try { updateStmt.run( fix.correct_timestamp, fix.correct_timestamp, fix.observation_id ); successCount++; console.log(`āœ… Fixed observation #${fix.observation_id}`); } catch (error) { errorCount++; console.error(`āŒ Failed to fix observation #${fix.observation_id}:`, error); } } console.log('\n═══════════════════════════════════════════════════════════════════════'); console.log('RESULTS:'); console.log('═══════════════════════════════════════════════════════════════════════'); console.log(`āœ… Successfully fixed: ${successCount}`); console.log(`āŒ Failed: ${errorCount}`); console.log(`šŸ“Š Total processed: ${fixes.length}\n`); if (successCount > 0) { console.log('šŸŽ‰ Timestamp corruption has been repaired!'); console.log('šŸ’” Next steps:'); console.log(' 1. Verify the fixes with: bun scripts/verify-timestamp-fix.ts'); console.log(' 2. Consider re-enabling orphan processing if timestamp fix is working\n'); } db.close(); } main();