Repository navigation
getMessage() returns { conversation: '' } on miss — Baileys relays an empty message and burns the retry, leaving "Waiting for this message" forever #2705
Description
Activity
Independent confirmation on v2.3.7, plus a consequence I think raises the severity of this
issue considerably: in group chats the retry you are burning is the only mechanism that can
ever repair a broken sender-key distribution.The code is still there in 2.3.7. From the running bundle:
async getMessage(key, full = false) { try { const rows = await this.prismaRepository.$queryRaw` SELECT * FROM "Message" WHERE "instanceId" = ${this.instanceId} AND "key"->>'id' = ${key.id}` if (full) return rows[0] if (rows[0].message?.pollCreationMessage) { /* ... */ } return rows[0].message } catch { return { conversation: '' } } }
Worth noting explicitly: on a miss the query returns
[], sorows[0].message?.…throws a
TypeError and lands in the samecatch. The "not found" path and the "database error" path
therefore converge on the same empty envelope — the miss is swallowed as if it were an error.Why this is worse in groups. In
baileys@7.0.0-rc.9— the version this project pins onmain
and on the2.4.0-rc2tag — the group sender-key bookkeeping map (sender-key-memory-<jid>@g.us)
is written to only withtrueand is never invalidated by participant or identity changes; the
comment// on participant change in group, we should do sender memory manipulationis still
unimplemented, in rc.9 and in currentmasteralike. The single place in the whole library that
clears that map issendMessagesAgaininsrc/Socket/messages-recv.ts:if (isJidGroup(remoteJid)) { await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } }) }
So the retry receipt is not merely one recovery path among several — for groups it is the only
one. Feeding it an empty envelope consumes the recipient's limited retries without ever delivering
real content, and once they are exhausted no further retry arrives. A transient decryption failure
becomes permanent.What we observe, on a fully LID-addressed group (Evolution 2.3.7, baileys 7.0.0-rc.9, Node
20.20.2, Redis-backed auth state, ~14 participants, all@lid):- Every send is accepted with no error, and a subset of members — all of them iPhone — see only
"Waiting for this message", indefinitely (> 1 week). Android members read everything. Messages
posted by humans in the same group are read by everyone, including the affected iPhones. - The group's
sender-key-memorymap is always 100%true— 29, 22, 30 and 22 device entries
observed on four separate occasions, always zerofalse. It never self-clears, which is itself
evidence that the retry path is not doing its job in practice. - Deleting that one Redis field restores delivery, confirmed on the affected handsets, 3 out of 3
times (2026-08-03, 08-08, 08-19), and it regresses within days: 4 events in 21 days. - One affected iPhone user created a new WhatsApp Business registration and immediately started
receiving the bot's group messages, with no change on our side — consistent with the map being the
gate, since a fresh registration is a device not yet markedtrue.
We now clear the map hourly from cron as a stop-gap. That is obviously not a fix, and it should not
be necessary.Supporting the proposed change: returning
undefinedinstead of{ conversation: '' }is the
right call. Two suggestions on top of it:- Distinguish the miss from the error —
if (!rows.length) return undefinedbefore touching
rows[0]— so a genuine database failure can be logged instead of silently masquerading as a
cache miss. - Log at
warnwhengetMessagemisses. Today this failure mode is completely silent on the
sender side: the socket reports success,Message.statusnever leavesPENDINGfor group sends,
and the only signal that anything is wrong is a human saying "I never got it".
Happy to test a patched build against the live group.
- Every send is accepted with no error, and a subset of members — all of them iPhone — see only
- added 2 commits that reference this issue
on Sep 27, 2026
What happened
getMessage()returns{ conversation: '' }when the message is not found. Baileys treats anytruthy return as "message found", so it relays an empty message and consumes one of the retry
attempts. On the recipient's phone the message becomes a permanent
"Waiting for this message. This may take a moment." placeholder — the retry that was supposed to
recover it delivered an empty envelope instead.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts(still onmaintoday):Baileys,
Socket/messages-recv.js:Returning
undefinedinstead makes Baileys take theelsebranch: no attempt is consumed andnothing is sent, so the peer can ask again and gets the real message once the row is committed.
Why the row can be missing
sendMessageWithTypingpersists the row after the send, after the Chatwoot integrationround-trip and, for media, after writing the media file — while
retryRequestDelayMsis 350 ms.Baileys' in-memory recent-message cache (512 entries) usually covers this, but it is lost on every
socket restart, so after a reconnect or a re-pair the DB path is the only one left. That is exactly
when a burst of retries happens (a re-pair invalidates every peer's session), so the failure
concentrates precisely where it hurts most.
We saw this in production right after a QR re-pair: group media sent 4 minutes later reached every
participant as the "Waiting for this message" placeholder, permanently.
Suggested fix
All 7 call sites of
this.getMessage(already handle a falsy return — and four of them getstrictly better, because today they silently receive a fake message and carry on:
getMessageoptionif (msg && ...)— relays the empty messagemessages.editf?.idh && (...)— aggregates votes against a fake messageS && (c = S)— quotes an empty messagegetBase64FromMediaMessageif (!n) throw 'Message not found'— never reached todayformatUpdateMessaget?.messageTypeupdateMessageif (!i) throw new BadRequestException('Message not found')— never reached todayAlso worth noting: the retry lookup filters on
"key"->>'id', and there is no index for it — it isa sequential scan of
Messageon every retry. Adding("instanceId", (("key"->>'id')))took the query from 9.76 ms to 0.158 ms on a 15.8k-row tablehere, and it only gets worse as the table grows.
Version
v2.3.6, self-hosted, Postgres, Chatwoot integration enabled.