Skip to content

fix: preserve reply metadata when flattening extendedTextMessage - #2708

Open
maykonjhonathang-cloud wants to merge 2 commits into
evolution-foundation:developfrom
maykonjhonathang-cloud:fix/preserve-quoted-context-on-text-messages
Open

fix: preserve reply metadata when flattening extendedTextMessage#2708
maykonjhonathang-cloud wants to merge 2 commits into
evolution-foundation:developfrom
maykonjhonathang-cloud:fix/preserve-quoted-context-on-text-messages

Conversation

@maykonjhonathang-cloud

@maykonjhonathang-cloud maykonjhonathang-cloud commented Aug 25, 2026

Copy link
Copy Markdown

📋 Description

prepareMessage() flattens extendedTextMessage into conversation and deletes the wrapper. A text reply carries its quote in extendedTextMessage.contextInfo (stanzaId, participant, quotedMessage), so deleting the wrapper drops the reply metadata before it reaches webhooks, the database and every integration.

The data-level contextInfo on the payload is built from messageContextInfo, which carries threadId / messageSecret / limitSharingV2 and never stanzaId — so nothing downstream can recover the quote.

Media replies are unaffected: imageMessage, audioMessage, stickerMessage and friends are never flattened, and their own contextInfo survives. 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:

  1. It puts the reply metadata exactly where the code immediately below already expects it. The const quotedMessage = messageRaw?.contextInfo?.quotedMessage block that normalizes the quoted preview is currently unreachable for text messages; this makes existing code work as written instead of introducing a new convention.
  2. messageRaw.message has already been through deserializeMessageBuffers() a few lines above, so no extra deserialization is needed. Existing messageContextInfo keys are spread first and therefore preserved — messageSecret and message editing are untouched.

🔗 Related Issue

Relates to #2078

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (if applicable)

Measured on an instance running 2.4.0-rc2 with real traffic (MySQL, 36,805 stored messages).

Before the fix:

messageType total with stanzaId
conversation 17,170 0
imageMessage 11,298 21
audioMessage 3,577 78
documentMessage 2,163 19
stickerMessage 260 12

After the fix: every connected session reconnected normally with no errors in the logs, and within minutes a real inbound text reply arrived carrying stanzaId in contextInfo, 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 contextInfo when the incoming message actually carries a quote.

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios

📝 Additional Notes

I did not add an automated test: package.json points test at ./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 develop and of the 2.4.0-rc2 image; 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:

  • Preserve quoted-message metadata when flattening text replies so webhook, database, and integration consumers can retain reply relationships.
  • Use the actual message-key participant when deriving sender names, preventing quoted-message authors from being misidentified as the current sender.

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.
@sourcery-ai

sourcery-ai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

The 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 metadata

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Preserve quote metadata when converting extended text messages to conversation messages.
  • Read contextInfo from the extended text wrapper before deleting it.
  • Merge reply metadata into the payload-level contextInfo while retaining existing message context fields.
  • Continue flattening the text content and remove the now-redundant extended text wrapper.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
`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.
@maykonjhonathang-cloud

Copy link
Copy Markdown
Author

Good catch from the review — the finding is valid, and I pushed a fix in 1685687.

The contextInfo.participant fallback in fetchMessages() was unreachable while the data-level contextInfo never carried a participant. Preserving the quote makes that branch live, and participant there is the author of the quoted message, so a text reply without pushName would have been labelled with the wrong sender.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant