Skip to content

Bug: Telegram photo inbound always sets bodyText=<media:image> regardless of actual contentType #69793

Description

@aspalagin

Summary

Telegram photo messages are always assigned bodyText = "<media:image>" in the inbound body builder, even when the actual contentType of the downloaded media is not image/*. This causes a mismatch between the text envelope seen by the model and the real media payload, leading to hallucinations and unreliable vision behavior.

Root cause

Two independent issues in the Telegram inbound pipeline:

1. bot-Ch7__EHu.js — unconditional <media:image> for any non-audio media

// BEFORE (buggy)
if (!bodyText && allMedia.length > 0)
  if (hasAudio) bodyText = preflightTranscript || '<media:audio>';
  else bodyText = `<media:image>${allMedia.length > 1 ? ` (${allMedia.length} images)` : ''}`;

Any non-audio attachment unconditionally becomes <media:image> in the body text, without checking contentType or mediaKindFromMime. If the actual media payload resolves to document or unknown downstream, the model receives contradictory signals.

2. delivery-AYrG1NE_.jsmsg.photo has no explicit mimeType

function resolveMediaMetadata(msg) {
  return {
    fileRef: msg.photo?.[msg.photo.length - 1] ?? msg.video ?? ...,
    mimeType: msg.audio?.mime_type ?? msg.voice?.mime_type ?? msg.video?.mime_type
           ?? msg.document?.mime_type ?? msg.animation?.mime_type
    // ^^^ msg.photo has no mime_type field in Telegram API — not handled here
  };
}

Telegram photo objects don't carry a mime_type field. Without an explicit fallback to "image/jpeg", the pipeline relies entirely on post-download MIME sniffing. If sniffing returns undefined or application/octet-stream, mediaKindFromMime returns undefined, which downstream treats as "unknown""document".

Impact

  • Model receives <media:image> in body text but no valid image input in the multimodal payload
  • Results in hallucinations, incorrect descriptions, or silent failures on Telegram photo messages
  • Intermittent / hard to reproduce because MIME sniffing usually succeeds for valid JPEGs, but fails under certain conditions (network issues during download, edge cases in file-type detection, etc.)

Fix

Fix 1 — build bodyText from actual contentType:

if (!bodyText && allMedia.length > 0)
  if (hasAudio) bodyText = preflightTranscript || '<media:audio>';
  else {
    const allImages = allMedia.every((m) => m.contentType?.startsWith('image/'));
    const kind = allImages ? 'image' : 'document';
    bodyText = `<media:${kind}>${allMedia.length > 1 ? ` (${allMedia.length} ${allImages ? 'images' : 'attachments'})` : ''}`;
  }

Fix 2 — explicit mimeType fallback for msg.photo:

mimeType: msg.audio?.mime_type ?? msg.voice?.mime_type ?? msg.video?.mime_type
       ?? msg.document?.mime_type ?? msg.animation?.mime_type
       ?? (Array.isArray(msg.photo) && msg.photo.length > 0 ? 'image/jpeg' : void 0)

Environment

  • OpenClaw version: 2026.4.15
  • Channel: Telegram
  • Files: dist/extensions/telegram/bot-Ch7__EHu.js, dist/extensions/telegram/delivery-AYrG1NE_.js

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions