Skip to content

fix(whatsapp): restore reply/quote contextInfo in prepareMessage#2654

Open
diegocesarmg wants to merge 1 commit into
evolution-foundation:developfrom
diegocesarmg:fix/reply-quote-contextinfo
Open

fix(whatsapp): restore reply/quote contextInfo in prepareMessage#2654
diegocesarmg wants to merge 1 commit into
evolution-foundation:developfrom
diegocesarmg:fix/reply-quote-contextinfo

Conversation

@diegocesarmg

@diegocesarmg diegocesarmg commented Jul 23, 2026

Copy link
Copy Markdown

Problem

Inbound (and outbound) text replies lose their quote/reply context. A message that quotes another arrives at the messages.upsert webhook — and is persisted, and returned by chat/findMessages — as messageType: "conversation" with no contextInfo.quotedMessage, no stanzaId, no participant. There is no way for any integration (Chatwoot, n8n, custom webhooks) to link a reply to the message it quotes.

This matches the long-standing report in #2078 ("Reply/Quote breaks on 2.3.3+") and is part of the @lid meta #1872, but the actual cause is a small regression in prepareMessage.

Root cause

In BaileysStartupService.prepareMessage, the message contextInfo is sourced from the wrong field:

contextInfo: this.deserializeMessageBuffers(message.message?.messageContextInfo),

message.message.messageContextInfo only carries device-list / encryption metadata (threadId, deviceListMetadata, messageSecret, limitSharingV2) — it never contains the quote. The reply context (stanzaId, participant, quotedMessage) lives on the content node, e.g. message.message.extendedTextMessage.contextInfo.

A few lines below, the extendedTextMessage node is flattened into conversation and deleted:

if (messageRaw.message.extendedTextMessage) {
  messageRaw.messageType = 'conversation';
  messageRaw.message.conversation = messageRaw.message.extendedTextMessage.text;
  delete messageRaw.message.extendedTextMessage;
}

So the quote is dropped entirely, and the messageRaw.contextInfo.quotedMessage handling right after it becomes dead code (it can never see a quotedMessage).

Regression window

Every stable release sourced contextInfo from the content node and quotes worked:

  • 2.3.2 / 2.3.3 / 2.3.7contextInfo: contentMsg?.contextInfo (with contentMsg = message.message[getContentType(...)])
  • develop / homologcontextInfo: message.message?.messageContextInfo

This is not a Baileys issue: 2.3.7 already runs Baileys 7.0.0-rc.9 and delivers the quote correctly. Only the source field changed.

Fix

Restore the content-node source, with a fallback to messageContextInfo so nothing is lost, and reuse the already-computed content type. The extendedTextMessage -> conversation normalization is intentionally kept unchanged.

const contentType = getContentType(message.message);
const contentMsg = contentType ? (message.message?.[contentType] as any) : undefined;
// ...
contextInfo: this.deserializeMessageBuffers(contentMsg?.contextInfo ?? message.message?.messageContextInfo),

After the fix, messages.upsert again exposes contextInfo.stanzaId / participant / quotedMessage for replies, and the downstream quotedMessage handling in prepareMessage works again. messageContextInfo remains available under messageRaw.message.messageContextInfo.

Refs #2078, #1872.

Summary by Sourcery

Bug Fixes:

  • Preserve reply/quote contextInfo (stanzaId, participant, quotedMessage) for WhatsApp messages by sourcing it from the message content node instead of the top-level messageContextInfo.

prepareMessage sourced the message contextInfo from
`message.message.messageContextInfo`, which only carries device-list /
encryption metadata (threadId, deviceListMetadata, messageSecret) and never
the quote. The reply context (stanzaId, participant, quotedMessage) lives on the
content node instead, e.g. `extendedTextMessage.contextInfo`.

Because the extendedTextMessage node is then flattened into `conversation` and
deleted, the quote was lost entirely: inbound (and outbound) replies reach the
`messages.upsert` webhook and the DB as plain `conversation` with no quotedMessage
or stanzaId, so no integration can link a reply to the message it quotes. The
existing `messageRaw.contextInfo.quotedMessage` handling right below became dead
code.

This restores the content-node source (`contentMsg?.contextInfo`) with a fallback
to `messageContextInfo`, matching the behaviour that shipped through v2.3.7. The
extendedTextMessage->conversation normalization is kept unchanged.

Refs evolution-foundation#2078, evolution-foundation#1872.

Co-Authored-By: Claude <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jul 23, 2026

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

Reviewer's Guide

Restores proper handling of WhatsApp reply/quote context in prepareMessage by sourcing contextInfo from the message content node while keeping existing normalization logic.

Flow diagram for restored contextInfo handling in prepareMessage

flowchart TD
  A[WAMessage.message] --> B[getContentType]
  B --> C[contentType]
  C --> D[contentMsg = message.message indexed by contentType]
  D --> E{contentMsg.contextInfo exists}
  E -- yes --> F[use contentMsg.contextInfo]
  E -- no --> G[use message.messageContextInfo]
  F --> H[deserializeMessageBuffers]
  G --> H[deserializeMessageBuffers]
  H --> I[Message.contextInfo]
Loading

File-Level Changes

Change Details Files
Restore reply/quote contextInfo by sourcing it from the message content node instead of top-level messageContextInfo, with a safe fallback and reuse of computed content type.
  • Compute contentType once via getContentType(message.message) and reuse it for messageType.
  • Derive contentMsg from the resolved contentType to access node-specific fields like contextInfo.
  • Change messageType assignment to use the previously computed contentType instead of calling getContentType again.
  • Update contextInfo population to deserialize from contentMsg.contextInfo, falling back to message.message.messageContextInfo when contentMsg has no contextInfo.
  • Document in comments the distinction between content-node contextInfo (quote/reply) and top-level messageContextInfo (device/encryption metadata) and why this ordering is required.
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 reviewed your changes and they look great!


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.

@diegocesarmg
diegocesarmg force-pushed the fix/reply-quote-contextinfo branch from d32def5 to 6692b38 Compare July 23, 2026 18:18
@dpaes

dpaes commented Jul 23, 2026

Copy link
Copy Markdown

@diegocesarmg have you tried in the release cantidates after 2.3.7? exists 2.4.0-rc1, 2.4.0-rc2 (both using baileys rc13)

@diegocesarmg

Copy link
Copy Markdown
Author

@dpaes thanks! Yes — I checked, and the bug is already present in 2.4.0-rc1 and 2.4.0-rc2.

It isn't a Baileys change. 2.3.7, 2.4.0-rc1 and 2.4.0-rc2 all pin the same Baileys 7.0.0-rc.9 (only develop/homolog moved to rc13, and it carries the same bug). So Baileys is not the variable here.

The regression is the prepareMessage rewrite in the 2.3.7 → 2.4.0-rc1 window, which changed where contextInfo is read from:

  • 2.3.7 (works): contextInfo: contentMsg?.contextInfo — the content node, which carries the quote
  • 2.4.0-rc1 / rc2 / develop (broken): contextInfo: message.message?.messageContextInfo — only device/encryption metadata (threadId, deviceListMetadata, messageSecret), never the quote

Because extendedTextMessage is then flattened into conversation and deleted, the quote (stanzaId / participant / quotedMessage) is lost entirely. Same Baileys across all three — only that one line differs.

This PR just restores the content-node source (with a fallback to messageContextInfo), matching the 2.3.7 behavior. Happy to add a test if useful.

@dpaes

dpaes commented Jul 24, 2026

Copy link
Copy Markdown

@pastoriniMatheus check this

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.

2 participants