# Session ID Usage Validation Test Failures Analysis **Report Date:** 2026-01-04 **Test File:** `tests/session_id_usage_validation.test.ts` **Category:** Session ID Usage Validation **Total Failures:** 10 (of 21 tests in file) --- ## 1. Executive Summary The 10 failing tests in the Session ID Usage Validation suite are caused by a **mismatch between the test expectations and the current implementation**. The tests were written based on an earlier design where `memory_session_id` was initialized as a placeholder equal to `content_session_id`. However, the current implementation initializes `memory_session_id` as `NULL`. ### Root Cause The implementation was changed to use `NULL` for `memory_session_id` initially, but the tests and documentation (`SESSION_ID_ARCHITECTURE.md`) still describe the old "placeholder" design. ### Key Discrepancy | Aspect | Tests Expect | Implementation Does | |--------|--------------|---------------------| | Initial `memory_session_id` | `= content_session_id` (placeholder) | `= NULL` | | Placeholder detection | `memory_session_id !== content_session_id` | `!!memory_session_id` (truthy check) | | FK for observations | Via `memory_session_id = content_session_id` | **Broken** - FK references NULL | --- ## 2. Test Analysis ### 2.1 Placeholder Detection Tests (3 failures) **Test Group:** `Placeholder Detection - hasRealMemorySessionId Logic` #### Test 1: "should identify placeholder when memorySessionId equals contentSessionId" **Expectation:** `session.memory_session_id === session.content_session_id` **Actual Result:** `session.memory_session_id = null` **Assertion:** `expect(session?.memory_session_id).toBe(session?.content_session_id)` fails because `null !== "user-session-123"` #### Test 2: "should identify real memory session ID after capture" **Status:** PASSES - This test correctly captures a memory session ID and verifies the change. #### Test 3: "should never use contentSessionId as resume parameter when in placeholder state" **Expectation:** Test logic checks `hasRealMemorySessionId = memory_session_id !== content_session_id` **Actual Result:** With `memory_session_id = null`, the expression evaluates incorrectly. --- ### 2.2 Observation Storage Tests (2 failures) **Test Group:** `Observation Storage - ContentSessionId Usage` #### Test 1: "should store observations with contentSessionId in memory_session_id column" **Error:** `SQLiteError: FOREIGN KEY constraint failed` **Root Cause:** - Test stores observation with `contentSessionId` as the `memory_session_id` - FK constraint: `FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id)` - `sdk_sessions.memory_session_id` is `NULL`, not `contentSessionId` - FK check fails because the value doesn't exist in the parent table #### Test 2: "should be retrievable using contentSessionId" **Error:** Same FK constraint failure as above --- ### 2.3 Resume Safety Tests (2 failures) **Test Group:** `Resume Safety - Prevent contentSessionId Resume Bug` #### Test 1: "should prevent resume with placeholder memorySessionId" **Expectation:** `hasRealMemorySessionId = (memory_session_id && memory_session_id !== content_session_id)` **Expected Result:** `false` (because they should be equal in placeholder state) **Actual Result:** Expression evaluates to `null` (falsy but not `false`) **Assertion:** `expect(hasRealMemorySessionId).toBe(false)` fails because `null !== false` #### Test 2: "should allow resume only after memory session ID is captured" **Same Issue:** The "before capture" state check fails with `null !== false` --- ### 2.4 Cross-Contamination Prevention (0 failures) **Status:** Both tests PASS - These work because they test behavior after `updateMemorySessionId()` is called. --- ### 2.5 Foreign Key Integrity Tests (2 failures) **Test Group:** `Foreign Key Integrity` #### Test 1: "should cascade delete observations when session is deleted" **Error:** `SQLiteError: FOREIGN KEY constraint failed` **Root Cause:** Cannot store observation because FK references `sdk_sessions.memory_session_id` which is `NULL`. #### Test 2: "should maintain FK relationship between observations and sessions" **Error:** Same FK constraint failure when storing valid observation. --- ### 2.6 Session Lifecycle Flow (1 failure) **Test Group:** `Session Lifecycle - Memory ID Capture Flow` #### Test: "should follow correct lifecycle: create -> capture -> resume" **Expectation:** Initial `memory_session_id` equals `content_session_id` (placeholder) **Actual:** `memory_session_id = NULL` **Assertion:** `expect(session?.memory_session_id).toBe(contentSessionId)` fails --- ### 2.7 1:1 Transcript Mapping Guarantees (2 failures) **Test Group:** `CRITICAL: 1:1 Transcript Mapping Guarantees` #### Test 1: "should enforce UNIQUE constraint on memory_session_id" **Status:** PASSES - Works because it tests behavior after capture #### Test 2: "should prevent memorySessionId from being changed after real capture" **Status:** PASSES but with a TODO note - Documents that the database layer doesn't prevent second updates #### Test 3: "should use same memorySessionId for all prompts in a conversation" **Error:** Initial placeholder assertion fails (`null !== "multi-prompt-session"`) #### Test 4: "should lookup session by contentSessionId and retrieve memorySessionId for resume" **Status:** PASSES - Works because it tests after capture --- ## 3. Current Implementation Status ### 3.1 SessionStore.createSDKSession() **Location:** `src/services/sqlite/SessionStore.ts` lines 1164-1182 ```typescript createSDKSession(contentSessionId: string, project: string, userPrompt: string): number { // ... // NOTE: memory_session_id starts as NULL. It is captured by SDKAgent from the first SDK // response and stored via updateMemorySessionId(). CRITICAL: memory_session_id must NEVER // equal contentSessionId - that would inject memory messages into the user's transcript! this.db.prepare(` INSERT OR IGNORE INTO sdk_sessions (content_session_id, memory_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, NULL, ?, ?, ?, ?, 'active') `).run(contentSessionId, project, userPrompt, now.toISOString(), nowEpoch); // ... } ``` **Key Point:** The comment explicitly states `memory_session_id` starts as `NULL` and warns against it ever equaling `contentSessionId`. ### 3.2 SDKAgent.startSession() **Location:** `src/services/worker/SDKAgent.ts` line 69 ```typescript const hasRealMemorySessionId = !!session.memorySessionId; ``` **Current Implementation:** Uses truthy check (`!!`), not equality comparison. ### 3.3 Documentation Mismatch **Location:** `docs/SESSION_ID_ARCHITECTURE.md` The documentation describes the OLD design where: - `memory_session_id = content_session_id` initially (placeholder) - `hasRealMemorySessionId = memory_session_id !== content_session_id` This documentation is now **incorrect** and mismatches the implementation. --- ## 4. Root Cause Analysis ### The Architecture Evolution 1. **Original Design (documented, tested):** - `memory_session_id` initialized to `content_session_id` as placeholder - Placeholder detection: `memory_session_id !== content_session_id` - Observations could use `content_session_id` value because FK matched 2. **Current Design (implemented):** - `memory_session_id` initialized to `NULL` - Placeholder detection: `!!memory_session_id` (truthy check) - Observations CANNOT use `content_session_id` because FK requires valid reference ### Why the Change Was Made The implementation comment reveals the reasoning: > "CRITICAL: memory_session_id must NEVER equal contentSessionId - that would inject memory messages into the user's transcript!" The change was made to prevent a potential security/data integrity issue where using `contentSessionId` for the memory session's resume parameter could cause messages to appear in the wrong conversation. ### The FK Problem The observations table has: ```sql FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id) ``` With `memory_session_id = NULL`: - Cannot store observations using `content_session_id` as the FK value - Cannot store observations at all until `memory_session_id` is captured - This may be **intentional** (observations only valid after SDK session established) --- ## 5. Recommended Fixes ### Option A: Update Tests to Match Implementation (Recommended) The current implementation is safer. Update tests to reflect the NULL-based design: 1. **Placeholder Detection Tests:** - Change expectations from `memory_session_id === content_session_id` to `memory_session_id === null` - Change `hasRealMemorySessionId` logic to `!!memory_session_id` 2. **Observation Storage Tests:** - Must call `updateMemorySessionId()` before storing observations - Or use a different test approach that captures memory session ID first 3. **Resume Safety Tests:** - Change expected value from `false` to `null` or use `.toBeFalsy()` 4. **Update Documentation:** - Rewrite `SESSION_ID_ARCHITECTURE.md` to reflect NULL-based initialization ### Option B: Revert to Placeholder Design Change implementation back to initialize with placeholder: 1. **Modify createSDKSession():** ```typescript VALUES (?, ?, ?, ?, ?, ?, 'active') // Pass contentSessionId as memory_session_id placeholder ``` 2. **Update SDKAgent hasRealMemorySessionId:** ```typescript const hasRealMemorySessionId = session.memorySessionId && session.memorySessionId !== session.contentSessionId; ``` 3. **Risk:** Need to validate that this doesn't cause the "transcript injection" issue mentioned in comments. ### Option C: Hybrid FK Design Keep NULL initialization but change FK relationship: 1. **Observations FK via content_session_id:** ```sql FOREIGN KEY(content_session_id) REFERENCES sdk_sessions(content_session_id) ``` 2. **Keep memory_session_id for data retrieval only** 3. **This requires schema migration** --- ## 6. Priority and Effort Estimate ### Priority: **HIGH** These failures indicate a fundamental mismatch between expected and actual behavior. The FK constraint failures are particularly concerning as they could affect production observation storage. ### Effort Estimate | Fix Option | Effort | Risk | Recommendation | |------------|--------|------|----------------| | Option A: Update Tests | 2-3 hours | Low | **Recommended** | | Option B: Revert Implementation | 1-2 hours | Medium | Not recommended | | Option C: Schema Change | 4-8 hours | High | Future consideration | ### Specific Changes for Option A 1. **`tests/session_id_usage_validation.test.ts`:** - Lines 39, 78, 149, 168, 320, 421: Change placeholder expectations from `content_session_id` to `null` - Lines 100, 127, 265, 285: Add `updateMemorySessionId()` call before storing observations - Lines 43, 60, 78, 149, 168, 177: Use `.toBeFalsy()` instead of `.toBe(false)` where appropriate 2. **`docs/SESSION_ID_ARCHITECTURE.md`:** - Update initialization flow diagram to show NULL initial state - Update placeholder detection logic description - Update observation storage section to clarify when observations can be stored --- ## 7. Test Summary | Test Category | Total | Pass | Fail | |--------------|-------|------|------| | Placeholder Detection | 3 | 1 | 2 | | Observation Storage | 2 | 0 | 2 | | Resume Safety | 2 | 0 | 2 | | Cross-Contamination | 2 | 2 | 0 | | Foreign Key Integrity | 2 | 0 | 2 | | Session Lifecycle | 2 | 1 | 1 | | 1:1 Transcript Mapping | 4 | 3 | 1 | | Edge Cases | 2 | 2 | 0 | | **TOTAL** | **21** | **10** | **10** | --- ## 8. Files Requiring Changes ### If Fixing Tests (Option A) 1. `tests/session_id_usage_validation.test.ts` - Update test expectations 2. `docs/SESSION_ID_ARCHITECTURE.md` - Update documentation ### If Reverting Implementation (Option B) 1. `src/services/sqlite/SessionStore.ts` - Change `createSDKSession()` to use placeholder 2. `src/services/worker/SDKAgent.ts` - Change `hasRealMemorySessionId` logic --- ## 9. References - **Test File:** `/Users/alexnewman/Scripts/claude-mem/tests/session_id_usage_validation.test.ts` - **Implementation:** `/Users/alexnewman/Scripts/claude-mem/src/services/sqlite/SessionStore.ts` - **SDKAgent:** `/Users/alexnewman/Scripts/claude-mem/src/services/worker/SDKAgent.ts` - **Documentation:** `/Users/alexnewman/Scripts/claude-mem/docs/SESSION_ID_ARCHITECTURE.md`