From 1c4160526256927f6d94f812e2e680d3e96a623a Mon Sep 17 00:00:00 2001 From: Iago Velasco Date: Mon, 14 Sep 2026 11:29:07 -0300 Subject: [PATCH] fix: do not resend an empty message when a retry receipt cannot be answered `getMessage` returned `{ conversation: '' }` whenever the message row was not found or the lookup threw. Baileys only skips a retry resend when `getMessage` resolves to a falsy value, so returning that object makes `sendMessagesAgain` relay an EMPTY message reusing the original message id. Group members then see a bubble with a timestamp and no content, repeated on every retry attempt (maxMsgRetryCount per requesting participant). It is easy to hit in groups: any member that fails to decrypt asks for a retry, and by then the original message may be gone from the DB (retention, instance recreated, or a message this instance never persisted). Measured on a 11-cell deployment before the fix: ~17.7k empty bubbles in 24h. After returning `undefined` instead, 10 of 11 cells dropped to zero, with the remainder coming from a third-party sender outside the deployment. No change in delivery of real messages. Also guards `webMessageInfo[0]` with optional chaining, so an empty result set no longer throws on `.message`, and reuses the local `message` variable in the poll branch. The poll and event decrypt callers in Baileys already guard with `if (msg)`, so they behave correctly with `undefined` too. Co-Authored-By: Claude Opus 5 (1M context) --- .../whatsapp/whatsapp.baileys.service.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 22839fd45..6596470fb 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -656,24 +656,38 @@ export class BaileysStartupService extends ChannelStartupService { if (full) { return webMessageInfo[0]; } - if (webMessageInfo[0].message?.pollCreationMessage) { - const messageSecretBase64 = webMessageInfo[0].message?.messageContextInfo?.messageSecret; + + // Baileys only skips a retry resend when getMessage resolves to a falsy value. Returning an + // object here (for example `{ conversation: '' }`) makes it relay an EMPTY message reusing the + // original message id, which shows up in the chat as a bubble with a timestamp and no content. + const message = webMessageInfo[0]?.message; + + if (!message) { + this.logger.debug(`getMessage: message ${key.id} not found, skipping retry resend`); + + return undefined; + } + + if (message.pollCreationMessage) { + const messageSecretBase64 = message.messageContextInfo?.messageSecret; if (typeof messageSecretBase64 === 'string') { const messageSecret = Buffer.from(messageSecretBase64, 'base64'); const msg = { messageContextInfo: { messageSecret }, - pollCreationMessage: webMessageInfo[0].message?.pollCreationMessage, + pollCreationMessage: message.pollCreationMessage, }; return msg; } } - return webMessageInfo[0].message; - } catch { - return { conversation: '' }; + return message; + } catch (error) { + this.logger.debug(`getMessage: lookup for ${key.id} failed, skipping retry resend: ${error?.toString()}`); + + return undefined; } }