Skip to content

Conversation

@kay0ramon
Copy link
Contributor

@kay0ramon kay0ramon commented Nov 28, 2025

This PR fixes the Error on upload file to minio error that occurs when processing messages containing only messageContextInfo without actual media content.

Problem

When WhatsApp sends media messages (audio, video, images) with ephemeral/viewOnce wrappers, the message structure gets transformed during processing. After the MessageSubtype loop transformation, some messages end up containing only messageContextInfo metadata without the actual media binary data. The previous code would throw an error in this
case, causing thousands of error logs like:

[
'Error on upload file to minio',
[ 'The message is messageContextInfo' ],
undefined
]

Solution

  1. Changed throw 'The message is messageContextInfo' to return null in getBase64FromMediaMessage() function
  2. Added null checks before attempting MinIO upload in both received and sent message handlers
  3. Messages without valid media content are now gracefully skipped with a verbose log instead of throwing errors

Changes Made

  • getBase64FromMediaMessage(): Returns null instead of throwing when message contains only messageContextInfo
  • Received messages upload (line ~1285): Added if (!media) return check
  • Sent messages upload (line ~2322): Added if (!media) return check

🔗 Related Issue

Closes #1026
Related: #1061, #1585, #1872

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

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

Testing Details

  • Tested in production environment with high message volume
  • Verified that regular media messages (audio, video, images, documents) continue to upload correctly to MinIO
  • Confirmed that messages with only messageContextInfo are now skipped gracefully
  • Error logs reduced from 4,648+ occurrences to zero
  • No impact on message delivery or storage functionality

📸 Screenshots (if applicable)

Before (error logs):
[ERROR] [ChannelStartupService] Error processing media message:
[ERROR] [ChannelStartupService] The message is messageContextInfo
[
'Error on upload file to minio',
[ 'The message is messageContextInfo' ],
undefined
]

After (clean logs):
[VERBOSE] [BaileysStartupService] Message contains only messageContextInfo, skipping media processing

✅ 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
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

Root Cause Analysis

The issue occurs due to the interaction between:

  1. Baileys 7.x message structure changes with LID/PN addressing
  2. MessageSubtype transformation that unwraps ephemeral/viewOnce messages
  3. hasValidMediaContent() check happening BEFORE the subtype transformation
  4. messageContextInfo check happening AFTER the transformation

This creates a race condition where a message passes the initial media check but fails after transformation.

Backward Compatibility

  • ✅ Messages with real media content continue working normally
  • ✅ MinIO uploads for valid media are unaffected
  • ✅ No changes to database schema or API contracts
  • ✅ Chatwoot integration (if used) has its own try/catch and will handle null gracefully

Summary by Sourcery

Gracefully skip WhatsApp messages that contain only messageContextInfo during media handling to avoid unnecessary MinIO upload errors and noisy logs.

Bug Fixes:

  • Prevent MinIO upload attempts for WhatsApp messages that contain only messageContextInfo metadata without actual media content, eliminating related runtime errors and log spam.

Enhancements:

  • Log verbose messages when media processing is skipped due to messageContextInfo-only payloads for both received and sent messages.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 28, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Handles WhatsApp messages that contain only messageContextInfo without media by treating them as non-media and short-circuiting MinIO upload, converting a previous throw into a null return and adding guard checks and verbose logs around media extraction and upload paths.

Sequence diagram for WhatsApp media upload with messageContextInfo handling

sequenceDiagram
  actor WhatsAppServer
  participant BaileysStartupService
  participant getBase64FromMediaMessage
  participant MinIO
  participant Logger

  WhatsAppServer ->> BaileysStartupService: onMediaMessage message
  BaileysStartupService ->> getBase64FromMediaMessage: getBase64FromMediaMessage message, isFromBaileys
  getBase64FromMediaMessage -->> BaileysStartupService: mediaOrNull

  alt mediaOrNull is null (messageContextInfo only)
    BaileysStartupService ->> Logger: verbose [No valid media to upload (messageContextInfo only), skipping MinIO]
    BaileysStartupService -->> WhatsAppServer: continue without MinIO upload
  else valid media object
    BaileysStartupService ->> MinIO: upload buffer, mediaType, fileName, size, mimetype
    MinIO -->> BaileysStartupService: uploadResult
    BaileysStartupService -->> WhatsAppServer: processing completed
  end
Loading

Flow diagram for getBase64FromMediaMessage messageContextInfo handling

flowchart TD
  A[Receive message in getBase64FromMediaMessage] --> B[Check if message contains messageContextInfo key]
  B -->|No| C[Proceed to media extraction]
  C --> D[Decode media binary data]
  D --> E[Build media object buffer, mediaType, fileName, size]
  E --> F[Return media object]

  B -->|Yes| G[Check if message has only messageContextInfo field]
  G -->|No| C
  G -->|Yes| H[Logger verbose Message contains only messageContextInfo, skipping media processing]
  H --> I[Return null to caller]
Loading

File-Level Changes

Change Details Files
Short-circuit MinIO upload when media extraction returns null for messageContextInfo-only messages.
  • After getBase64FromMediaMessage in the inbound media handling flow, added a null/falsey check on the media result and early return with a verbose log when there is no valid media.
  • After getBase64FromMediaMessage in the outbound media handling flow, added a null/falsey check on the media result and early return with a verbose log when there is no valid media.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Treat messageContextInfo-only messages as non-media by returning null instead of throwing.
  • In getBase64FromMediaMessage, detect messages where message.message consists only of messageContextInfo and replace the thrown string error with a verbose log and a null return value so callers can handle this case gracefully.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Assessment against linked issues

Issue Objective Addressed Explanation
#1026 Fix the MinIO upload errors (including TypeError/ERR_INVALID_ARG_TYPE) that occur when processing WhatsApp media messages which contain only messageContextInfo and no actual media content.
#1026 Ensure WhatsApp media messages (e.g., images) are processed without crashing so they can be correctly handled by downstream integrations like Chatwoot instead of failing during upload.

Possibly linked issues

  • #0: PR changes MinIO media handling for messageContextInfo-only messages, addressing the audio upload errors seen in the issue.

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

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Now that getBase64FromMediaMessage can return null, its return type and any related typings/interfaces should be updated to explicitly reflect the nullable result so future callers are forced to handle this case.
  • The new if (!media) return; checks inside the upload logic change the control flow; verify that returning from these scopes is intended (i.e., that it only skips processing of the current message and not the entire higher-level operation), and consider using a more localized guard (e.g., continue in loops or early if blocks) if needed.
  • The verbose log message for skipping messageContextInfo-only media is duplicated in two places; consider extracting it into a shared helper or constant to keep the behavior and wording consistent if it needs to change later.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Now that `getBase64FromMediaMessage` can return `null`, its return type and any related typings/interfaces should be updated to explicitly reflect the nullable result so future callers are forced to handle this case.
- The new `if (!media) return;` checks inside the upload logic change the control flow; verify that returning from these scopes is intended (i.e., that it only skips processing of the current message and not the entire higher-level operation), and consider using a more localized guard (e.g., `continue` in loops or early `if` blocks) if needed.
- The verbose log message for skipping messageContextInfo-only media is duplicated in two places; consider extracting it into a shared helper or constant to keep the behavior and wording consistent if it needs to change later.

## Individual Comments

### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:3700-3701` </location>
<code_context>

       if ('messageContextInfo' in msg.message && Object.keys(msg.message).length === 1) {
-        throw 'The message is messageContextInfo';
+        this.logger.verbose('Message contains only messageContextInfo, skipping media processing');
+        return null;
       }

</code_context>

<issue_to_address>
**question (bug_risk):** Changing from throwing to returning `null` alters error semantics and caller expectations.

Previously this code threw (`'The message is messageContextInfo'`), which would surface as an error. Now it only logs (verbose) and returns `null`. Any remaining callers that still assume a non-null return (e.g. destructure or use `media` as a resolved object) may now misbehave. Please audit all `getBase64FromMediaMessage` usages to ensure they handle `null`, or consider a more explicit result type (e.g. `{ kind: 'no-media' }`) to make this state safer to consume.
</issue_to_address>

### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:1282-1292` </location>
<code_context>
            if (isMedia) {
              if (this.configService.get<S3>('S3').ENABLE) {
                try {
                  if (isVideo && !this.configService.get<S3>('S3').SAVE_VIDEO) {
                    this.logger.warn('Video upload is disabled. Skipping video upload.');
                    // Skip video upload by returning early from this block
                    return;
                  }

                  const message: any = received;

                  // Verificação adicional para garantir que há conteúdo de mídia real
                  const hasRealMedia = this.hasValidMediaContent(message);

                  if (!hasRealMedia) {
                    this.logger.warn('Message detected as media but contains no valid media content');
                  } else {
                    const media = await this.getBase64FromMediaMessage({ message }, true);

                    if (!media) {
                      this.logger.verbose('No valid media to upload (messageContextInfo only), skipping MinIO');
                      return;
                    }

                    const { buffer, mediaType, fileName, size } = media;
                    const mimetype = mimeTypes.lookup(fileName).toString();
                    const fullName = join(
                      `${this.instance.id}`,
                      received.key.remoteJid,
                      mediaType,
                      `${Date.now()}_${fileName}`,
                    );
                    await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, { 'Content-Type': mimetype });

                    await this.prismaRepository.media.create({
                      data: {
                        messageId: msg.id,
                        instanceId: this.instanceId,
                        type: mediaType,
                        fileName: fullName,
                        mimetype,
                      },
                    });

                    const mediaUrl = await s3Service.getObjectUrl(fullName);

                    messageRaw.message.mediaUrl = mediaUrl;

                    await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
                  }
                } catch (error) {
                  this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
                }
              }
            }

</code_context>

<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/merge-nested-ifs))

```suggestion
            if (isMedia && this.configService.get<S3>('S3').ENABLE) {
                  try {
                    if (isVideo && !this.configService.get<S3>('S3').SAVE_VIDEO) {
                      this.logger.warn('Video upload is disabled. Skipping video upload.');
                      // Skip video upload by returning early from this block
                      return;
                    }

                    const message: any = received;

                    // Verificação adicional para garantir que há conteúdo de mídia real
                    const hasRealMedia = this.hasValidMediaContent(message);

                    if (!hasRealMedia) {
                      this.logger.warn('Message detected as media but contains no valid media content');
                    } else {
                      const media = await this.getBase64FromMediaMessage({ message }, true);

                      if (!media) {
                        this.logger.verbose('No valid media to upload (messageContextInfo only), skipping MinIO');
                        return;
                      }

                      const { buffer, mediaType, fileName, size } = media;
                      const mimetype = mimeTypes.lookup(fileName).toString();
                      const fullName = join(
                        `${this.instance.id}`,
                        received.key.remoteJid,
                        mediaType,
                        `${Date.now()}_${fileName}`,
                      );
                      await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, { 'Content-Type': mimetype });

                      await this.prismaRepository.media.create({
                        data: {
                          messageId: msg.id,
                          instanceId: this.instanceId,
                          type: mediaType,
                          fileName: fullName,
                          mimetype,
                        },
                      });

                      const mediaUrl = await s3Service.getObjectUrl(fullName);

                      messageRaw.message.mediaUrl = mediaUrl;

                      await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
                    }
                  } catch (error) {
                    this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
                  }
            }

```

<br/><details><summary>Explanation</summary>Reading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>

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.

@allefrodrigo
Copy link

Resolveu aqui, muito obrigado!

@wagnerfnds
Copy link

Boa! Deu certo! Obrigado!!

@itsfabioroma
Copy link

Issue scaled significantly. Apparently, not only with viewOnce chats anymore.

We need to handle in a more robust way, because most of the times, a standalone call to /getBase64FromMediaMessage works to get the base64 and display media properly on Evolution Manager or handle it for S3/MinIO..

Anyone have managed to get around this?

@DavidsonGomes DavidsonGomes changed the base branch from main to develop December 5, 2025 13:51
@DavidsonGomes
Copy link
Collaborator

Please, fix the lint with npm run lint

@DavidsonGomes DavidsonGomes merged commit 3864366 into EvolutionAPI:develop Dec 5, 2025
4 of 5 checks passed
@kay0ramon kay0ramon deleted the fix/minio-messagecontextinfo-upload-error branch December 5, 2025 13:59
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.

Erro no envio de mídia na instancia WhatsApp Cloud API

6 participants