fix: preserve reply metadata when flattening extendedTextMessage - #2708
Conversation
A text reply carries its quote in extendedTextMessage.contextInfo (stanzaId, participant, quotedMessage). prepareMessage() copied only .text into conversation and deleted the wrapper, dropping the reply metadata before it reached webhooks, the database and integrations. The data-level contextInfo comes from messageContextInfo, which carries threadId/messageSecret/limitSharingV2 and never stanzaId, so nothing downstream could recover it. Media replies were unaffected because they are never flattened. The quote is now merged into contextInfo before the wrapper is dropped — which is where the quotedMessage normalization right below already expects it.
Reviewer's guide (collapsed on small PRs)Reviewer's GuideThe fix prevents text replies from losing their quoted-message metadata during extendedTextMessage flattening by merging the wrapper’s contextInfo into the existing payload contextInfo before the wrapper is deleted; media-message handling remains unchanged. Sequence diagram for preserving text reply metadatasequenceDiagram
participant Baileys as BaileysStartupService
participant Payload as MessagePayload
participant Consumer as WebhooksDatabaseIntegrations
Baileys->>Payload: prepareMessage()
Baileys->>Payload: deserializeMessageBuffers()
Baileys->>Payload: Read extendedTextMessage.contextInfo
opt quotedContext exists
Baileys->>Payload: Merge quotedContext into contextInfo
end
Baileys->>Payload: Set messageType to conversation
Baileys->>Payload: Set message.conversation from extendedTextMessage.text
Baileys->>Payload: Delete message.extendedTextMessage
Payload-->>Consumer: Message with contextInfo.stanzaId and quotedMessage
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="5199" />
<code_context>
+ const quotedContext = messageRaw.message.extendedTextMessage.contextInfo;
+
+ if (quotedContext) {
+ messageRaw.contextInfo = { ...(messageRaw.contextInfo ?? {}), ...quotedContext };
+ }
+
</code_context>
<issue_to_address>
**issue (broader_impact):** When a text reply has no stored `pushName`, `fetchMessages()` treats the newly copied `contextInfo.participant` as the sender and assigns the quoted message author's JID as `pushName`, because it checks `contextInfo.participant` before `message.key.participant`. This mislabels the reply's sender in fetched message results.
**Triggers:** When an inbound text reply lacks `pushName` and its quoted message has a `participant` field.
**Suggested fix:** In `fetchMessages()`, prefer `messageKey.participant` for the sender fallback, or distinguish the quoted participant from the sender before exposing it at this level.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and this copies reply metadata into the flattened message, where it can be persisted and exposed through webhooks; if the merge is wrong, reverting will not remove metadata already stored or delivered. The impact is bounded and can be corrected or recomputed, rather than causing irreversible deletion, access, or financial effects.
Blocking findings: src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:5199
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
`fetchMessages()` fell back to `contextInfo.participant` before `messageKey.participant` when a message had no pushName. That branch was unreachable while contextInfo never carried a participant; with the quote preserved, it would label a text reply with the author of the message it quotes. The key is the only field that identifies who sent THIS message, so it now comes first, and the quoted participant stays as a last resort.
|
Good catch from the review — the finding is valid, and I pushed a fix in The The message key is the only field that identifies who sent this message, so it now comes first; the quoted participant stays as a last resort rather than being removed, to keep the change narrow. |
📋 Description
prepareMessage()flattensextendedTextMessageintoconversationand deletes the wrapper. A text reply carries its quote inextendedTextMessage.contextInfo(stanzaId,participant,quotedMessage), so deleting the wrapper drops the reply metadata before it reaches webhooks, the database and every integration.The data-level
contextInfoon the payload is built frommessageContextInfo, which carriesthreadId/messageSecret/limitSharingV2and neverstanzaId— so nothing downstream can recover the quote.Media replies are unaffected:
imageMessage,audioMessage,stickerMessageand friends are never flattened, and their owncontextInfosurvives. The result is that the quote is lost based on what the reply is, not on what it quotes — replying with audio keeps the quote, replying with text loses it.This PR keeps the quote before dropping the wrapper. Two notes on the shape of the fix:
const quotedMessage = messageRaw?.contextInfo?.quotedMessageblock that normalizes the quoted preview is currently unreachable for text messages; this makes existing code work as written instead of introducing a new convention.messageRaw.messagehas already been throughdeserializeMessageBuffers()a few lines above, so no extra deserialization is needed. ExistingmessageContextInfokeys are spread first and therefore preserved —messageSecretand message editing are untouched.🔗 Related Issue
Relates to #2078
🧪 Type of Change
🧪 Testing
Measured on an instance running
2.4.0-rc2with real traffic (MySQL, 36,805 stored messages).Before the fix:
stanzaIdAfter the fix: every connected session reconnected normally with no errors in the logs, and within minutes a real inbound text reply arrived carrying
stanzaIdincontextInfo, in both the sender's and the receiver's copy. The consuming CRM stored the reply linked to its quoted message — something that had never happened on this installation.The change is additive: it only adds keys to
contextInfowhen the incoming message actually carries a quote.✅ Checklist
📝 Additional Notes
I did not add an automated test:
package.jsonpointstestat./test/all.test.ts, which is not present in the repository, so there is no suite to extend. Happy to add one if you can point me at where it should live.I also did not bisect the 2.3.2 → 2.3.3 boundary mentioned in #2078. What is verified here is the behavior of current
developand of the2.4.0-rc2image; the symptom matches that report, but I cannot confirm it is the same change that caused that regression.Summary by Sourcery
Preserve reply context and correctly identify senders for flattened WhatsApp text messages.
Bug Fixes: