refactor: remove session management complexity and fix timestamp handling
- Simplify createSDKSession to pure INSERT OR IGNORE - Remove auto-create logic from storeObservation/storeSummary - Delete 11 unused session management methods - Derive prompt_number from user_prompts count - Fix corrupted observation timestamps and validate logic - Keep sdk_sessions table schema unchanged for compatibility
This commit is contained in:
@@ -1,405 +0,0 @@
|
||||
# Phased Execution Plan: Remove Session Management Logic
|
||||
|
||||
**Goal**: Delete problematic session management code while keeping schema unchanged
|
||||
|
||||
**Approach**: Stop using certain columns/methods, but don't change the database structure
|
||||
|
||||
---
|
||||
|
||||
## PHASE 1: Simplify createSDKSession()
|
||||
|
||||
**File**: `src/services/sqlite/SessionStore.ts`
|
||||
|
||||
**Task**: Replace `createSDKSession()` with a pure INSERT OR IGNORE version
|
||||
|
||||
**Current code** (~line 1142-1178):
|
||||
- Does INSERT OR IGNORE
|
||||
- Then tries to UPDATE project/user_prompt if they changed
|
||||
- Complex logic
|
||||
|
||||
**New code**:
|
||||
```typescript
|
||||
createSDKSession(claudeSessionId: string, project: string, userPrompt: string): number {
|
||||
const now = new Date();
|
||||
const nowEpoch = now.getTime();
|
||||
|
||||
// Pure INSERT OR IGNORE - no updates, no complexity
|
||||
this.db.prepare(`
|
||||
INSERT OR IGNORE INTO sdk_sessions
|
||||
(claude_session_id, sdk_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'active')
|
||||
`).run(claudeSessionId, claudeSessionId, project, userPrompt, now.toISOString(), nowEpoch);
|
||||
|
||||
// Return existing or new ID
|
||||
const row = this.db.prepare('SELECT id FROM sdk_sessions WHERE claude_session_id = ?')
|
||||
.get(claudeSessionId) as { id: number };
|
||||
return row.id;
|
||||
}
|
||||
```
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -A20 "createSDKSession(" src/services/sqlite/SessionStore.ts | head -25
|
||||
# Should see simple INSERT OR IGNORE, no UPDATE logic
|
||||
```
|
||||
|
||||
**Output**: Simplified createSDKSession method
|
||||
|
||||
---
|
||||
|
||||
## PHASE 2: Add getPromptNumberFromUserPrompts() Helper
|
||||
|
||||
**File**: `src/services/sqlite/SessionStore.ts`
|
||||
|
||||
**Task**: Add a new helper method to derive prompt number from user_prompts table
|
||||
|
||||
**Add this method**:
|
||||
```typescript
|
||||
/**
|
||||
* Get current prompt number by counting user_prompts for this session
|
||||
* Replaces the prompt_counter column which is no longer maintained
|
||||
*/
|
||||
getPromptNumberFromUserPrompts(claudeSessionId: string): number {
|
||||
const result = this.db.prepare(`
|
||||
SELECT COUNT(*) as count FROM user_prompts WHERE claude_session_id = ?
|
||||
`).get(claudeSessionId) as { count: number };
|
||||
return result.count;
|
||||
}
|
||||
```
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -n "getPromptNumberFromUserPrompts" src/services/sqlite/SessionStore.ts
|
||||
# Should find the new method
|
||||
```
|
||||
|
||||
**Output**: New helper method added
|
||||
|
||||
---
|
||||
|
||||
## PHASE 3: Delete Unused Session Management Methods
|
||||
|
||||
**File**: `src/services/sqlite/SessionStore.ts`
|
||||
|
||||
**Task**: Delete these 11 methods entirely:
|
||||
|
||||
1. `updateSDKSessionId()` (~line 1185-1205)
|
||||
2. `findActiveSDKSession()` (~line 1043-1057)
|
||||
3. `findAnySDKSession()` (~line 1062-1071)
|
||||
4. `reactivateSession()` (~line 1076-1084)
|
||||
5. `incrementPromptCounter()` (~line 1089-1103)
|
||||
6. `getPromptCounter()` (~line 1108-1114)
|
||||
7. `setWorkerPort()` (~line 1210-1218)
|
||||
8. `getWorkerPort()` (~line 1223-1233)
|
||||
9. `markSessionCompleted()` (~line 1419-1430)
|
||||
10. `markSessionFailed()` (~line 1435-1446)
|
||||
11. `getSdkSessionsBySessionIds()` (~line 1017-1038) - if unused
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
# These should return no results after deletion
|
||||
grep -n "incrementPromptCounter\|getPromptCounter\|setWorkerPort\|getWorkerPort" src/services/sqlite/SessionStore.ts
|
||||
grep -n "markSessionCompleted\|markSessionFailed\|reactivateSession" src/services/sqlite/SessionStore.ts
|
||||
grep -n "findActiveSDKSession\|findAnySDKSession\|updateSDKSessionId" src/services/sqlite/SessionStore.ts
|
||||
```
|
||||
|
||||
**Output**: 11 methods deleted from SessionStore.ts
|
||||
|
||||
---
|
||||
|
||||
## PHASE 4: Remove Auto-Create from storeObservation()
|
||||
|
||||
**File**: `src/services/sqlite/SessionStore.ts`
|
||||
|
||||
**Task**: Remove session auto-creation logic from storeObservation()
|
||||
|
||||
**Find** (~line 1291-1312): The block that checks if session exists and creates it if not
|
||||
|
||||
**Delete** this pattern:
|
||||
```typescript
|
||||
// DELETE THIS BLOCK:
|
||||
let sessionId = this.db.prepare(`SELECT id FROM sdk_sessions WHERE sdk_session_id = ?`).get(sdkSessionId);
|
||||
if (!sessionId) {
|
||||
// auto-create session...
|
||||
}
|
||||
```
|
||||
|
||||
**Keep**: Just the INSERT INTO observations statement
|
||||
|
||||
The method should assume the session already exists. If it doesn't, the INSERT will fail with a foreign key error - which is correct behavior (means the hook is broken).
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -B5 -A15 "storeObservation(" src/services/sqlite/SessionStore.ts | grep -i "insert.*sdk_sessions"
|
||||
# Should return nothing - no session creation in storeObservation
|
||||
```
|
||||
|
||||
**Output**: storeObservation() no longer auto-creates sessions
|
||||
|
||||
---
|
||||
|
||||
## PHASE 5: Remove Auto-Create from storeSummary()
|
||||
|
||||
**File**: `src/services/sqlite/SessionStore.ts`
|
||||
|
||||
**Task**: Remove session auto-creation logic from storeSummary()
|
||||
|
||||
**Find** (~line 1367-1388): Similar pattern to storeObservation
|
||||
|
||||
**Delete** the session existence check and auto-create block
|
||||
|
||||
**Keep**: Just the INSERT INTO session_summaries statement
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -B5 -A15 "storeSummary(" src/services/sqlite/SessionStore.ts | grep -i "insert.*sdk_sessions"
|
||||
# Should return nothing - no session creation in storeSummary
|
||||
```
|
||||
|
||||
**Output**: storeSummary() no longer auto-creates sessions
|
||||
|
||||
---
|
||||
|
||||
## PHASE 6: Update SessionRoutes - Replace getPromptCounter
|
||||
|
||||
**File**: `src/services/worker/http/routes/SessionRoutes.ts`
|
||||
|
||||
**Task**: Replace calls to `getPromptCounter()` with `getPromptNumberFromUserPrompts()`
|
||||
|
||||
**Find**: All calls to `store.getPromptCounter(sessionDbId)`
|
||||
|
||||
**Replace with**: `store.getPromptNumberFromUserPrompts(claudeSessionId)`
|
||||
|
||||
Note: The new method takes `claudeSessionId` (string), not `sessionDbId` (number)
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -n "getPromptCounter" src/services/worker/http/routes/SessionRoutes.ts
|
||||
# Should return nothing
|
||||
|
||||
grep -n "getPromptNumberFromUserPrompts" src/services/worker/http/routes/SessionRoutes.ts
|
||||
# Should find the new calls
|
||||
```
|
||||
|
||||
**Output**: SessionRoutes uses new prompt counting method
|
||||
|
||||
---
|
||||
|
||||
## PHASE 7: Update SessionManager - Replace getPromptCounter
|
||||
|
||||
**File**: `src/services/worker/SessionManager.ts`
|
||||
|
||||
**Task**: Replace call to `getPromptCounter()` in `initializeSession()`
|
||||
|
||||
**Find** (~line 116): `this.dbManager.getSessionStore().getPromptCounter(sessionDbId)`
|
||||
|
||||
**Replace with**:
|
||||
```typescript
|
||||
this.dbManager.getSessionStore().getPromptNumberFromUserPrompts(dbSession.claude_session_id)
|
||||
```
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -n "getPromptCounter" src/services/worker/SessionManager.ts
|
||||
# Should return nothing
|
||||
```
|
||||
|
||||
**Output**: SessionManager uses new prompt counting method
|
||||
|
||||
---
|
||||
|
||||
## PHASE 8: Update SessionCompletionHandler
|
||||
|
||||
**File**: `src/services/worker/session/SessionCompletionHandler.ts`
|
||||
|
||||
**Task**: Remove call to `findActiveSDKSession()` and simplify
|
||||
|
||||
**Find**: Call to `store.findActiveSDKSession(claudeSessionId)`
|
||||
|
||||
**Replace**: Use a simpler query or remove the lookup if not needed
|
||||
|
||||
If the method just needs to find the session to delete it, we can query sdk_sessions directly:
|
||||
```typescript
|
||||
const session = store.db.prepare(
|
||||
'SELECT id FROM sdk_sessions WHERE claude_session_id = ?'
|
||||
).get(claudeSessionId);
|
||||
```
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -n "findActiveSDKSession" src/services/worker/session/SessionCompletionHandler.ts
|
||||
# Should return nothing
|
||||
```
|
||||
|
||||
**Output**: SessionCompletionHandler no longer uses deleted method
|
||||
|
||||
---
|
||||
|
||||
## PHASE 9: Update SDKAgent - Remove markSessionCompleted
|
||||
|
||||
**File**: `src/services/worker/SDKAgent.ts`
|
||||
|
||||
**Task**: Remove call to `markSessionCompleted()`
|
||||
|
||||
**Find** (~line 148): `this.dbManager.getSessionStore().markSessionCompleted(session.sessionDbId)`
|
||||
|
||||
**Action**: Delete this line entirely. We no longer track session status.
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -n "markSessionCompleted\|markSessionFailed" src/services/worker/SDKAgent.ts
|
||||
# Should return nothing
|
||||
```
|
||||
|
||||
**Output**: SDKAgent no longer marks sessions as completed
|
||||
|
||||
---
|
||||
|
||||
## PHASE 10: Update DatabaseManager - Remove markSessionComplete
|
||||
|
||||
**File**: `src/services/worker/DatabaseManager.ts`
|
||||
|
||||
**Task**: Remove the `markSessionComplete()` method
|
||||
|
||||
**Find** (~line 116-118): The `markSessionComplete` method
|
||||
|
||||
**Action**: Delete the entire method
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
grep -n "markSessionComplete" src/services/worker/DatabaseManager.ts
|
||||
# Should return nothing
|
||||
```
|
||||
|
||||
**Output**: DatabaseManager no longer has session completion method
|
||||
|
||||
---
|
||||
|
||||
## PHASE 11: Search for Any Remaining References
|
||||
|
||||
**Task**: Find and fix any remaining references to deleted methods
|
||||
|
||||
**Search**:
|
||||
```bash
|
||||
# Search for all deleted method names
|
||||
grep -rn "incrementPromptCounter\|getPromptCounter\|setWorkerPort\|getWorkerPort" src/ --include="*.ts"
|
||||
grep -rn "markSessionCompleted\|markSessionFailed\|reactivateSession" src/ --include="*.ts"
|
||||
grep -rn "findActiveSDKSession\|findAnySDKSession\|updateSDKSessionId" src/ --include="*.ts"
|
||||
```
|
||||
|
||||
**Action**: Fix any remaining references found
|
||||
|
||||
**Output**: No references to deleted methods remain
|
||||
|
||||
---
|
||||
|
||||
## PHASE 12: Build and Test
|
||||
|
||||
**Task**: Verify everything compiles and works
|
||||
|
||||
**Commands**:
|
||||
```bash
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# If build fails, fix TypeScript errors and rebuild
|
||||
```
|
||||
|
||||
**Verification**:
|
||||
- Build completes without errors
|
||||
- No TypeScript errors about missing methods
|
||||
|
||||
**Output**: Clean build
|
||||
|
||||
---
|
||||
|
||||
## PHASE 13: Integration Test
|
||||
|
||||
**Task**: Test the complete flow
|
||||
|
||||
**Test steps**:
|
||||
1. Start a new Claude Code session
|
||||
2. Submit a prompt (triggers new-hook → createSDKSession)
|
||||
3. Use some tools (triggers save-hook → storeObservation)
|
||||
4. End session (triggers summary-hook → storeSummary)
|
||||
|
||||
**Verify in database**:
|
||||
```bash
|
||||
sqlite3 ~/.claude-mem/claude-mem.db "SELECT id, claude_session_id, project, status FROM sdk_sessions ORDER BY id DESC LIMIT 3;"
|
||||
sqlite3 ~/.claude-mem/claude-mem.db "SELECT id, sdk_session_id, type, title FROM observations ORDER BY id DESC LIMIT 5;"
|
||||
```
|
||||
|
||||
**Expected**:
|
||||
- Sessions created with status='active' (never updated, that's fine)
|
||||
- Observations saved correctly
|
||||
- No errors in worker logs
|
||||
|
||||
**Output**: System works end-to-end
|
||||
|
||||
---
|
||||
|
||||
## PHASE 14: Cleanup Plan Files
|
||||
|
||||
**Task**: Delete the planning files
|
||||
|
||||
```bash
|
||||
rm PLAN-REMOVE-SESSION-MANAGEMENT.md
|
||||
rm PHASED-EXECUTION-PLAN.md
|
||||
rm PHASED-EXECUTION-PLAN-SIMPLE.md
|
||||
```
|
||||
|
||||
**Commit**:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "refactor: remove session management complexity
|
||||
|
||||
- Simplify createSDKSession to pure INSERT OR IGNORE
|
||||
- Remove auto-create logic from storeObservation/storeSummary
|
||||
- Delete 11 unused session management methods
|
||||
- Derive prompt_number from user_prompts count
|
||||
- Keep sdk_sessions table schema unchanged for compatibility"
|
||||
```
|
||||
|
||||
**Output**: Clean commit with simplified session handling
|
||||
|
||||
---
|
||||
|
||||
## SUCCESS CRITERIA
|
||||
|
||||
After all phases complete, verify:
|
||||
|
||||
- [ ] `createSDKSession()` is pure INSERT OR IGNORE (no updates)
|
||||
- [ ] `storeObservation()` has no session auto-create logic
|
||||
- [ ] `storeSummary()` has no session auto-create logic
|
||||
- [ ] 11 session management methods deleted
|
||||
- [ ] `getPromptNumberFromUserPrompts()` exists and is used
|
||||
- [ ] Build completes without errors
|
||||
- [ ] Normal flow works (prompt → tools → observations → summary)
|
||||
- [ ] No references to deleted methods in codebase
|
||||
- [ ] Database schema unchanged (no migration needed)
|
||||
|
||||
---
|
||||
|
||||
## WHAT WE STOPPED USING (BUT DIDN'T DELETE FROM SCHEMA)
|
||||
|
||||
These columns in `sdk_sessions` are now dead:
|
||||
|
||||
| Column | Previously | Now |
|
||||
|--------|-----------|-----|
|
||||
| `status` | Updated to 'completed'/'failed' | Always 'active', never updated |
|
||||
| `completed_at` | Set on session end | Never set |
|
||||
| `completed_at_epoch` | Set on session end | Never set |
|
||||
| `worker_port` | Tracked which worker | Never set |
|
||||
| `prompt_counter` | Incremented per prompt | Ignored, derived from user_prompts |
|
||||
|
||||
These columns still work fine but we just don't write to them anymore. Existing data is preserved.
|
||||
|
||||
---
|
||||
|
||||
## PHILOSOPHY
|
||||
|
||||
**Before**: Complex session lifecycle management with status tracking, port assignment, prompt counting, auto-creation fallbacks
|
||||
|
||||
**After**: Simple lookup table. INSERT OR IGNORE on first prompt. That's it.
|
||||
|
||||
If data is missing, it's a bug in the hook. Fail loudly, don't paper over it.
|
||||
@@ -1,145 +0,0 @@
|
||||
# Timestamp Fix Validation Report
|
||||
|
||||
**Date**: Dec 24, 2025
|
||||
**Status**: ✅ VALIDATED - Logic is correct and working
|
||||
|
||||
## Summary
|
||||
|
||||
The backlog timestamp fix has been validated. All 171 corrupted observations have been repaired, and the code logic for preventing future corruption is correct.
|
||||
|
||||
## What Was Fixed
|
||||
|
||||
- **Total corrupted observations**: 171
|
||||
- **Date range**: Oct 26 - Dec 24, 2025
|
||||
- **Root cause**: Observations from old sessions being processed late got current timestamps instead of original timestamps
|
||||
- **Fix applied**: Restored all observations to their correct original timestamps
|
||||
|
||||
## Validation Results
|
||||
|
||||
### 1. Database Integrity ✅
|
||||
|
||||
```
|
||||
Corrupted observations remaining: 0
|
||||
Pending messages with issues: 0
|
||||
```
|
||||
|
||||
### 2. Code Logic ✅
|
||||
|
||||
The timestamp override logic flows correctly:
|
||||
|
||||
1. **SessionManager.yieldNextMessage()** (src/services/worker/SessionManager.ts:451-454)
|
||||
- Tracks `earliestPendingTimestamp` when yielding messages
|
||||
- Uses `Math.min()` to keep the earliest timestamp across batches
|
||||
|
||||
2. **SDKAgent.createMessageGenerator()** (src/services/worker/SDKAgent.ts:209)
|
||||
- Calls `getMessageIterator()` which yields messages
|
||||
- earliestPendingTimestamp is set BEFORE messages are sent to Claude
|
||||
|
||||
3. **SDKAgent response handling** (src/services/worker/SDKAgent.ts:119)
|
||||
- Captures `originalTimestamp = session.earliestPendingTimestamp`
|
||||
- This happens when Claude RESPONDS, after messages were already yielded
|
||||
|
||||
4. **SDKAgent.processSDKResponse()** (src/services/worker/SDKAgent.ts:272, 350)
|
||||
- Passes `originalTimestamp ?? undefined` to storage methods
|
||||
- Both observations and summaries use this timestamp
|
||||
|
||||
5. **SessionStore.storeObservation/storeSummary()** (src/services/sqlite/SessionStore.ts:1157, 1210)
|
||||
- Uses `overrideTimestampEpoch ?? Date.now()`
|
||||
- If override provided (from backlog), uses that
|
||||
- Otherwise uses current time (for new messages)
|
||||
|
||||
6. **SDKAgent.markMessagesProcessed()** (src/services/worker/SDKAgent.ts:430)
|
||||
- Resets `earliestPendingTimestamp = null` after batch completes
|
||||
- Ready for next batch with fresh timestamp tracking
|
||||
|
||||
### 3. Sequence Validation ✅
|
||||
|
||||
**Correct sequence for backlog messages:**
|
||||
|
||||
```
|
||||
Time Event earliestPendingTimestamp
|
||||
------ ---------------------------------------------- ------------------------
|
||||
T1 yieldNextMessage() called → Set to msg.created_at_epoch
|
||||
T2 Messages sent to Claude SDK → Still set
|
||||
T3 Claude responds → Still set
|
||||
T4 Capture originalTimestamp → Captured (equals T1 timestamp)
|
||||
T5 Create observations with originalTimestamp → Uses T1 timestamp ✅
|
||||
T6 Mark messages processed → Reset to null
|
||||
```
|
||||
|
||||
**Correct sequence for new messages:**
|
||||
|
||||
```
|
||||
Time Event earliestPendingTimestamp
|
||||
------ ---------------------------------------------- ------------------------
|
||||
T1 yieldNextMessage() called (recent message) → Set to msg.created_at_epoch (recent)
|
||||
T2 Messages sent to Claude SDK → Still set
|
||||
T3 Claude responds → Still set
|
||||
T4 Capture originalTimestamp → Captured (equals T1 timestamp)
|
||||
T5 Create observations with originalTimestamp → Uses T1 timestamp ✅
|
||||
T6 Mark messages processed → Reset to null
|
||||
```
|
||||
|
||||
In both cases, observations get the timestamp from when the message was originally created, not when the observation was saved.
|
||||
|
||||
## Current State
|
||||
|
||||
### Pending Messages
|
||||
- **6 pending messages** (all from Dec 24, 2025)
|
||||
- **0 stuck messages** (status='processing')
|
||||
- All pending messages would be processed with correct timestamps if orphan processing enabled
|
||||
|
||||
### Orphan Processing
|
||||
- Currently **DISABLED** in `src/services/worker-service.ts:479`
|
||||
- **Safe to re-enable** - timestamp fix is working correctly
|
||||
- No risk of future timestamp corruption
|
||||
|
||||
## Scripts Created
|
||||
|
||||
1. **`scripts/fix-all-timestamps.ts`** - Comprehensive fix for ALL corrupted timestamps
|
||||
```bash
|
||||
bun scripts/fix-all-timestamps.ts --dry-run # Preview
|
||||
bun scripts/fix-all-timestamps.ts --yes # Apply
|
||||
```
|
||||
|
||||
2. **`scripts/validate-timestamp-logic.ts`** - Validate the backlog timestamp logic
|
||||
```bash
|
||||
bun scripts/validate-timestamp-logic.ts
|
||||
```
|
||||
|
||||
3. **`scripts/verify-timestamp-fix.ts`** - Verify specific time window
|
||||
```bash
|
||||
bun scripts/verify-timestamp-fix.ts
|
||||
```
|
||||
|
||||
## Recommendation
|
||||
|
||||
✅ **Safe to re-enable orphan processing**
|
||||
|
||||
The timestamp fix is working correctly. To re-enable:
|
||||
|
||||
```typescript
|
||||
// src/services/worker-service.ts:479
|
||||
// Change from:
|
||||
// this.processOrphanedQueues(pendingStore).catch((err: Error) => {
|
||||
|
||||
// To:
|
||||
this.processOrphanedQueues(pendingStore).catch((err: Error) => {
|
||||
logger.warn('SYSTEM', 'Orphan queue processing failed', {}, err);
|
||||
});
|
||||
```
|
||||
|
||||
## Files Changed in Fix
|
||||
|
||||
- `src/services/sqlite/SessionStore.ts` - Added `overrideTimestampEpoch` parameter
|
||||
- `src/services/worker-types.ts` - Added `earliestPendingTimestamp` to ActiveSession
|
||||
- `src/services/worker/SessionManager.ts` - Tracks earliest timestamp when yielding
|
||||
- `src/services/worker/SDKAgent.ts` - Passes timestamp through to storage
|
||||
- `src/services/worker-service.ts` - Orphan processing (currently disabled)
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ All corrupted timestamps fixed (171 observations)
|
||||
✅ Code logic validated and working correctly
|
||||
✅ No remaining timestamp issues in database
|
||||
✅ Safe to re-enable orphan processing
|
||||
@@ -1,117 +0,0 @@
|
||||
# ✅ FIXED: Corrupted Observation Timestamps
|
||||
|
||||
## What Happened
|
||||
|
||||
On Dec 24, 2025, orphan queue processing was enabled without a working timestamp fix. This caused 25 observations from old sessions (Dec 16-22) to be saved with Dec 24 timestamps instead of their original timestamps.
|
||||
|
||||
## The Fix - COMPLETED
|
||||
|
||||
**Status**: ✅ All corrupted timestamps have been repaired
|
||||
|
||||
**Date Fixed**: Dec 24, 2025 at 20:55 PM PST
|
||||
|
||||
**Results**:
|
||||
- Fixed 25 observations with corrupted timestamps
|
||||
- Observations restored to correct dates (Dec 16-22)
|
||||
- Remaining 31 observations in the window are legitimate (from Dec 24 sessions)
|
||||
|
||||
## The Original Damage
|
||||
|
||||
Observations created between approximately 19:45-20:31 on Dec 24 had wrong timestamps. These observations came from processing old pending_messages that were stuck in "processing" status.
|
||||
|
||||
## How to Identify Affected Observations
|
||||
|
||||
The affected observations were created from pending_messages that had `created_at_epoch` from Dec 17-20, but the observations got Dec 24 timestamps.
|
||||
|
||||
```sql
|
||||
-- Find observations created during the bad window (Dec 24 ~19:45-20:31)
|
||||
SELECT id, sdk_session_id,
|
||||
datetime(created_at_epoch/1000, 'unixepoch', 'localtime') as obs_time,
|
||||
title
|
||||
FROM observations
|
||||
WHERE created_at_epoch >= 1735098300000 -- Dec 24 19:45
|
||||
AND created_at_epoch <= 1735101060000 -- Dec 24 20:31
|
||||
ORDER BY id;
|
||||
```
|
||||
|
||||
## How to Fix
|
||||
|
||||
### Option 1: Match via processed pending_messages
|
||||
|
||||
The pending_messages table has `completed_at_epoch` which shows when they were processed, and `created_at_epoch` which has the CORRECT original timestamp.
|
||||
|
||||
```sql
|
||||
-- Find pending_messages processed during the bad window
|
||||
SELECT id, session_db_id, tool_name,
|
||||
datetime(created_at_epoch/1000, 'unixepoch', 'localtime') as original_time,
|
||||
datetime(completed_at_epoch/1000, 'unixepoch', 'localtime') as processed_time
|
||||
FROM pending_messages
|
||||
WHERE status = 'processed'
|
||||
AND completed_at_epoch >= 1735098300000
|
||||
AND completed_at_epoch <= 1735101060000
|
||||
ORDER BY completed_at_epoch;
|
||||
```
|
||||
|
||||
### Option 2: Match by session and sequence
|
||||
|
||||
For each affected session, observations should be ordered by their original pending_message timestamps:
|
||||
|
||||
```sql
|
||||
-- Get the correct timestamp for each observation by matching session
|
||||
-- This requires correlating sdk_session_id with session_db_id
|
||||
```
|
||||
|
||||
### Fix Script Approach
|
||||
|
||||
1. Get all pending_messages processed during the bad window
|
||||
2. For each, get its `session_db_id` and `created_at_epoch` (original timestamp)
|
||||
3. Find observations for that session created during the bad window
|
||||
4. Update observations with the correct `created_at_epoch` from their source pending_message
|
||||
|
||||
```sql
|
||||
-- Example fix for a single observation (after identifying the correct timestamp):
|
||||
UPDATE observations
|
||||
SET created_at_epoch = [correct_epoch],
|
||||
created_at = datetime([correct_epoch]/1000, 'unixepoch')
|
||||
WHERE id = [observation_id];
|
||||
```
|
||||
|
||||
## The Timestamp Fix (Already Deployed)
|
||||
|
||||
The code now correctly passes `overrideTimestampEpoch` through:
|
||||
- `SessionStore.storeObservation()` - accepts optional timestamp override
|
||||
- `SessionStore.storeSummary()` - accepts optional timestamp override
|
||||
- `ActiveSession.earliestPendingTimestamp` - tracks original message timestamp
|
||||
- `SDKAgent.processSDKResponse()` - passes timestamp to storage
|
||||
|
||||
## Scripts Created
|
||||
|
||||
Three scripts were created to handle this issue:
|
||||
|
||||
1. **`scripts/fix-corrupted-timestamps.ts`** - Identifies and repairs corrupted timestamps
|
||||
```bash
|
||||
bun scripts/fix-corrupted-timestamps.ts --dry-run # Preview fixes
|
||||
bun scripts/fix-corrupted-timestamps.ts --yes # Apply fixes
|
||||
```
|
||||
|
||||
2. **`scripts/verify-timestamp-fix.ts`** - Verifies the fix was successful
|
||||
```bash
|
||||
bun scripts/verify-timestamp-fix.ts
|
||||
```
|
||||
|
||||
3. **`scripts/investigate-timestamps.ts`** - Investigates timestamp data
|
||||
```bash
|
||||
bun scripts/investigate-timestamps.ts
|
||||
```
|
||||
|
||||
## Orphan Processing Status
|
||||
|
||||
The timestamp fix has been verified and is working correctly. Orphan processing can now be safely re-enabled in `src/services/worker-service.ts` if needed.
|
||||
|
||||
## Files Changed
|
||||
|
||||
- `src/services/sqlite/SessionStore.ts` - added overrideTimestampEpoch param
|
||||
- `src/services/worker-types.ts` - added earliestPendingTimestamp to ActiveSession
|
||||
- `src/services/worker/SessionManager.ts` - tracks earliest timestamp when yielding
|
||||
- `src/services/worker/SDKAgent.ts` - passes timestamp through processSDKResponse
|
||||
- `src/services/worker-service.ts` - orphan processing disabled
|
||||
Reference in New Issue
Block a user