From 209a55a8b7865101b2ac171b2caf223650731539 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Feb 2026 03:11:57 +0000 Subject: [PATCH] fix(messages-recv): wrap session deletions in transactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL FIX: Wraps session deletion operations in transactions to prevent race conditions with concurrent session operations. Changes: - Wrap session deletion at line 468 (incoming retry) in transaction - Wrap session deletion at line 1026 (outgoing retry) in transaction - Use transaction key format: delete-session-${sessionId} Problem before fix: Session deletions happened OUTSIDE transactions while other operations INSIDE transactions could be reading/writing the same session key. Timeline of race condition: T0: Message A arrives → processingMutex.mutex() T1: Transaction started → reads session X T2: Message B (retry) → shouldRecreateSession() T3: Message B deletes session X ← OUTSIDE transaction T4: Message A tries to use session X in transaction T5: Session doesn't exist → decryption failure T6: Message A lost After fix: All session operations (read/write/delete) are serialized via transactions, preventing concurrent access and data corruption. https://claude.ai/code/session_VMxqX --- src/Socket/messages-recv.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index d499a88c..60acb489 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -465,7 +465,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { if (shouldRecreateSession) { logger.debug({ fromJid, retryCount, reason: recreateReason, errorCode }, 'recreating session for retry') // Delete existing session to force recreation - await authState.keys.set({ session: { [sessionId]: null } }) + // CRITICAL: Wrap in transaction to prevent race with other session operations + await authState.keys.transaction(async () => { + await authState.keys.set({ session: { [sessionId]: null } }) + }, `delete-session-${sessionId}`) forceIncludeKeys = true } } catch (error) { @@ -1023,7 +1026,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { if (shouldRecreateSession) { logger.debug({ participant, retryCount, reason: recreateReason, errorCode }, 'recreating session for outgoing retry') - await authState.keys.set({ session: { [sessionId]: null } }) + // CRITICAL: Wrap in transaction to prevent race with other session operations + await authState.keys.transaction(async () => { + await authState.keys.set({ session: { [sessionId]: null } }) + }, `delete-session-${sessionId}`) } } catch (error) { logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry')