fix(baileys): resolve @lid to phone JID across history sync and live messages - #2663
Open
fidelis05 wants to merge 2 commits into
Open
fix(baileys): resolve @lid to phone JID across history sync and live messages#2663fidelis05 wants to merge 2 commits into
fidelis05 wants to merge 2 commits into
Conversation
…messages WhatsApp increasingly addresses events with LID identifiers (<lid>@lid) instead of the phone-based JID. Business accounts hit this almost exclusively, which left those instances with no usable chat history and messages that never linked to their chats. Two distinct problems are fixed: 1. messages.upsert resolved @lid only *after* the message row was written, so the Message row, the Chat lookup and the Contact upsert were all keyed by @lid while the swap only patched the outgoing webhook payload. Resolution now happens before any lookup or write. received.key itself is left untouched so protocol calls (readMessages, requestPlaceholderResend, fetchMessageHistory, media download) keep addressing the message exactly as WhatsApp delivered it. 2. messaging-history.set never resolved @lid on messages at all, and its chat resolution only consulted contact.phoneNumber - which history payloads frequently omit. History-sync message keys carry no remoteJidAlt or addressingMode, so resolution now goes through Baileys' signal-level LID store (signalRepository.lidMapping), batched once per payload and shared by chats, contacts and messages. The lookup is guarded and degrades to previous behaviour when the store is unavailable. Measured on a Business-linked instance (1101 messages, 91 chats): @lid messages 1035 -> 0 @lid chats 29 -> 2 (2 have no mapping, name or messages) chats with >1 message 3 -> 47 Verified for live messages.upsert as well: LID-addressed messages in both directions, including media, resolve to the phone JID and land in the existing chat rather than creating a parallel @lid one. Also includes patches/baileys+7.0.0-rc13.patch, fixing two upstream bugs that made media on those messages unrecoverable: - downloadMediaMessage tested error?.status, but getHttpStream throws a Boom carrying the status on output.statusCode, so the re-upload request was never sent. - getMediaRetryKey did not normalise a base64-string mediaKey the way getMediaKeys does, so the retry request was encrypted with a key derived from the base64 characters and WhatsApp replied DECRYPTION_ERROR. With both applied, media within WhatsApp's ~30 day retention window downloads again (verified 10/10, output validated as real Ogg/Opus). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Reviewer's GuideThis PR ensures WhatsApp LID-based identifiers are resolved to phone JIDs consistently for both history sync and live messages, and patches upstream Baileys media-download bugs, so that chats, messages, and media are correctly linked and retrievable. Sequence diagram for resolving LID to phone JID on live messages.upsertsequenceDiagram
actor WhatsApp
participant BaileysClient
participant BaileysStartupService
participant PrismaChat as PrismaRepository.chat
participant PrismaMessage as PrismaRepository.message
participant Webhook as DataWebhook
WhatsApp->>BaileysClient: incoming message (key.remoteJid = lid@lid)
BaileysClient->>BaileysStartupService: messageHandle.messages.upsert(received)
BaileysStartupService->>BaileysStartupService: prepare resolvedRemoteJid / resolvedParticipant
alt [resolvedRemoteJid differs from received.key.remoteJid]
BaileysStartupService->>BaileysStartupService: build messageForPersist with resolved key
else [no LID]
BaileysStartupService->>BaileysStartupService: messageForPersist = received
end
BaileysStartupService->>PrismaChat: chat.findFirst({ instanceId, remoteJid: resolvedRemoteJid })
PrismaChat-->>BaileysStartupService: existingChat or null
BaileysStartupService->>BaileysStartupService: prepareMessage(messageForPersist)
BaileysStartupService->>PrismaMessage: message.create({ data: messageData })
PrismaMessage-->>BaileysStartupService: msg
BaileysStartupService->>Webhook: sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw)
Webhook-->>BaileysStartupService: ack
BaileysStartupService->>PrismaChat: contact.findFirst({ remoteJid: resolvedRemoteJid })
BaileysStartupService->>BaileysStartupService: profilePicture(resolvedRemoteJid)
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The JID/LID detection logic mixes
includes('@lid')andendsWith('@lid')checks in different places; consider standardising on one approach (likelyendsWith) or centralising this in a helper to avoid subtle mismatches on non-user JIDs. - The
historySyncLidToJidMapcache is only cleared onsyncType === 'INITIAL_STATUS_V4'but is reused across multiplemessaging-history.setbatches; double-check that this lifetime is intentional and that mappings from previous history windows or older sessions cannot leak into unrelated payloads.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The JID/LID detection logic mixes `includes('@lid')` and `endsWith('@lid')` checks in different places; consider standardising on one approach (likely `endsWith`) or centralising this in a helper to avoid subtle mismatches on non-user JIDs.
- The `historySyncLidToJidMap` cache is only cleared on `syncType === 'INITIAL_STATUS_V4'` but is reused across multiple `messaging-history.set` batches; double-check that this lifetime is intentional and that mappings from previous history windows or older sessions cannot leak into unrelated payloads.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Addresses review feedback on evolution-foundation#2663: the added LID handling mixed includes('@lid') and endsWith('@lid'). endsWith is the stricter and correct check for a JID suffix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
WhatsApp increasingly addresses events with LID identifiers (
<lid>@lid) instead of the phone-based JID. Business-linked accounts hit this almost exclusively, which leaves those instances with no usable chat history and live messages that never link to their chat.This is the concrete failure behind the umbrella issue #1872.
Root cause
Two distinct problems, both in
whatsapp.baileys.service.ts:1.
messages.upsertresolved@lidtoo late.The swap ran after
prismaRepository.message.create(), after theChatlookup and after theContactupsert — so all three were keyed by@lidwhile the swap only patched the outgoing webhook payload. The stored row was never corrected.2.
messaging-history.setnever resolved@lidon messages at all.Its chat resolution consulted only
contact.phoneNumber, which history payloads frequently omit. Worse, history-sync message keys carry noremoteJidAltand noaddressingMode— verified directly against stored rows — so there is nothing on the key to resolve from.Because chats resolved (partially) while messages did not, messages and chats ended up keyed differently and simply never joined. That is why chats appeared to contain only their most recent message.
Fix
messages.upsertnow happens before any lookup or write.received.keyitself is deliberately left untouched so protocol calls (readMessages,requestPlaceholderResend,fetchMessageHistory, media download) keep addressing the message exactly as WhatsApp delivered it; only a resolved copy is used for our own storage.signalRepository.lidMapping), batched once per payload and shared by chats, contacts and messages. The lookup is guarded (if (!lidStore?.getPNsForLIDs) return;) and degrades to previous behaviour if unavailable.contactsMapLidJidfalls back to the contact's own id, socontact.jidcould be truthy but still@lid— the old truthy check swallowed it and blocked resolution.Results
Measured on a Business-linked instance (1101 messages, 91 chats):
@lidmessages@lidchatsThe 2 remaining
@lidchats have no name, no messages and no mapping in the LID store — nothing exists to resolve them to.Live
messages.upsertverified separately: LID-addressed messages in both directions, including media, resolve to the phone JID and land in the existing chat rather than creating a parallel@lidone. Unread counters and contact linkage confirmed correct.Also included:
patches/baileys+7.0.0-rc13.patchMedia on these messages was unrecoverable due to two upstream Baileys bugs (submitted separately to WhiskeySockets/Baileys):
downloadMediaMessagetestserror?.status, butgetHttpStreamthrows a Boom carrying the status onoutput.statusCode..statusis alwaysundefined, so the media re-upload request was never sent — unreachable dead code.getMediaRetryKeydoes not normalise a base64-stringmediaKeythe way its siblinggetMediaKeysdoes. Media keys rehydrated from JSON storage are base64, so HKDF ran over the base64 characters instead of the key bytes, and WhatsApp answeredDECRYPTION_ERROR.With both applied, media inside WhatsApp's retention window downloads again — verified 10/10, output validated as genuine
Ogg/Opuswith byte length matching the declared size.Testing notes / limitations
client.signalRepository.lidMapping, which is Baileys-internal. It is guarded and degrades gracefully, but it could shift across Baileys versions.tsc --noEmitandeslintboth clean.Summary by Sourcery
Resolve WhatsApp LID-based identifiers to phone JIDs consistently for history sync and live messages, and patch upstream Baileys media download issues.
Bug Fixes:
Enhancements: