From 805244fa5d8a40f8add08dc2f8fc28c4e290c9c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Feb 2026 03:54:50 +0000 Subject: [PATCH] fix(messages-recv): use consistent transaction key for session operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/Socket/messages-recv.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index 60acb489..7176377c 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -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')