openclaw - ✅(Solved) Fix Bug: Telegram photo inbound always sets bodyText=<media:image> regardless of actual contentType [1 pull requests, 3 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#69793Fetched 2026-04-22 07:48:14
View on GitHub
Comments
3
Participants
3
Timeline
5
Reactions
0
Author
Timeline (top)
commented ×3cross-referenced ×1referenced ×1

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:

Fix Action

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)

PR fix notes

PR #69893: fix(telegram): correct media type classification for photo inbound (#69793)

Description (problem / solution / changelog)

Fixes #69793 — Telegram photo inbound was always setting bodyText=media:image regardless of actual content type. Now uses contentType to classify as image/video/document correctly.

Changed files

  • extensions/browser/src/browser/pw-session.test.ts (modified, +75/-0)
  • extensions/browser/src/browser/pw-session.ts (modified, +65/-0)
  • extensions/browser/src/browser/pw-tools-core.browser-ssrf-guard.test.ts (modified, +1/-0)
  • extensions/browser/src/browser/pw-tools-core.snapshot.ts (modified, +10/-1)
  • extensions/telegram/src/bot-message-context.body.ts (modified, +10/-1)
  • extensions/whatsapp/src/auto-reply.web-auto-reply.last-route.test.ts (modified, +109/-0)
  • extensions/whatsapp/src/auto-reply/monitor/on-message.ts (modified, +6/-0)
  • src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.custom-provider-payloads.test.ts (added, +113/-0)
  • src/agents/pi-embedded-subscribe.ts (modified, +5/-3)
  • src/agents/sandbox/remote-fs-bridge.test.ts (modified, +57/-0)
  • src/agents/sandbox/remote-fs-bridge.ts (modified, +16/-2)
  • src/infra/system-events.test.ts (modified, +31/-0)
  • src/infra/system-events.ts (modified, +6/-1)
  • src/tasks/task-registry.audit.test.ts (modified, +77/-0)
  • src/tasks/task-registry.ts (modified, +7/-4)

Code Example

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

---

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
  };
}

---

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'})` : ''}`;
  }

---

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)
RAW_BUFFERClick to expand / collapse

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

extent analysis

TL;DR

Apply fixes to bot-Ch7__EHu.js and delivery-AYrG1NE_.js to correctly handle contentType and provide an explicit mimeType fallback for msg.photo.

Guidance

  • Update bot-Ch7__EHu.js to build bodyText based on the actual contentType of the media, rather than unconditionally assigning <media:image>.
  • Modify delivery-AYrG1NE_.js to include an explicit mimeType fallback for msg.photo, defaulting to 'image/jpeg' when msg.photo is present.
  • Verify the fixes by testing with various media types and ensuring the bodyText and mimeType are correctly set.
  • Monitor the model's behavior to confirm that hallucinations and incorrect descriptions are reduced or eliminated.

Example

The provided code snippets in the issue demonstrate the necessary changes:

// 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)

Notes

These fixes assume that the contentType and mimeType fields are correctly set in the media objects. If issues persist, further investigation into the media processing pipeline may be necessary.

Recommendation

Apply the provided fixes to bot-Ch7__EHu.js and delivery-AYrG1NE_.js to address the root causes of the issue. This should improve the model's behavior when handling Telegram photo messages.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Bug: Telegram photo inbound always sets bodyText=<media:image> regardless of actual contentType [1 pull requests, 3 comments, 3 participants]