fix(messages-recv): use consistent transaction key for session operations

CRITICAL FIX: Addresses Codex Bot comment on PR #79 about transaction key mismatch.

Problem:
Session delete operations used `delete-session-${sessionId}` as transaction key,
while encrypt/decrypt operations in sendMessage() use `meId` as key. Different
keys = different mutexes = operations can run concurrently = race condition.

Timeline before fix:
T0: sendMessage() → transaction(meId) → mutex_meId acquired
T1: Encrypt uses session X
T2: shouldRecreateSession() → transaction(delete-session-X) → mutex_delete acquired
T3: Delete session X ← CONCURRENT!
T4: sendMessage() tries to use session X → CRASH

Changes:
- Line 472: Change key from `delete-session-${sessionId}` to `authState.creds.me?.id`
- Line 1034: Same change for outgoing retry deletion
- Now all session operations (read/write/delete/encrypt) share same mutex
- Operations are properly serialized, preventing concurrent access

After fix:
T0: sendMessage() → transaction(meId) → mutex_meId acquired
T1: shouldRecreateSession() → transaction(meId) → waits for mutex
T2: sendMessage() completes → mutex released
T3: shouldRecreateSession() acquires mutex → deletes session safely

Validation:
-  Both session deletions now use same key as encrypt operations
-  Cross-file contract respected (messages-send.ts:1385 uses meId)
-  Race condition eliminated via mutex serialization

https://claude.ai/code/session_VMxqX
This commit is contained in:
Claude
2026-02-04 03:54:50 +00:00
parent f460d7ddf9
commit 805244fa5d
+6 -4
View File
@@ -465,10 +465,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
if (shouldRecreateSession) {
logger.debug({ fromJid, retryCount, reason: recreateReason, errorCode }, 'recreating session for retry')
// Delete existing session to force recreation
// CRITICAL: Wrap in transaction to prevent race with other session operations
// CRITICAL: Use same transaction key as encrypt/decrypt operations to prevent race
// Using meId ensures this delete serializes with sendMessage() and other session operations
await authState.keys.transaction(async () => {
await authState.keys.set({ session: { [sessionId]: null } })
}, `delete-session-${sessionId}`)
}, authState.creds.me?.id || 'session-operation')
forceIncludeKeys = true
}
} catch (error) {
@@ -1026,10 +1027,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
if (shouldRecreateSession) {
logger.debug({ participant, retryCount, reason: recreateReason, errorCode }, 'recreating session for outgoing retry')
// CRITICAL: Wrap in transaction to prevent race with other session operations
// CRITICAL: Use same transaction key as encrypt/decrypt operations to prevent race
// Using meId ensures this delete serializes with sendMessage() and other session operations
await authState.keys.transaction(async () => {
await authState.keys.set({ session: { [sessionId]: null } })
}, `delete-session-${sessionId}`)
}, authState.creds.me?.id || 'session-operation')
}
} catch (error) {
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry')