Telegram image files sent as documents misrouted

NousResearch/hermes-agent · cluster 18620 · gateway

Data as of

Nothing open on GitHub. Fix merged in #28519. Check it covers your case.

The short answer

0open issues
0open PRs
7items grouped
Known?
Yes. 7 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #28519. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this unresolved. That is separate from the fix event.
Still open
Nothing open on GitHub.

A bug where image files (PNG, JPG, WebP, GIF) sent as Telegram documents were rejected or mishandled instead of being routed through image/vision handling. Multiple fix attempts address routing these document-type images correctly.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #28519 fix(telegram): route image documents (.png/.jpg/.webp/.gif) through vision pipeline

    merged to main

    files: gateway/platforms/base.py, gateway/platforms/telegram.py, scripts/release.py, tests/gateway/test_telegram_documents.py

    Show the diff
    • gateway/platforms/base.py
      @@ -837,6 +837,26 @@ def cache_video_from_bytes(data: bytes, ext: str = ".mp4") -> str:
       }
       
       
      +# ---------------------------------------------------------------------------
      +# Image document types
      +#
      +# Image extensions that platforms may deliver as "documents" rather than
      +# native photo attachments (Telegram users uploading via the file picker,
      +# clients that wrap stickers/screenshots as files, etc.). When we see one
      +# of these, we route the bytes through the image cache and the normal
      +# vision/photo handling path instead of rejecting them as unsupported
      +# documents.
      +# ---------------------------------------------------------------------------
      +
      +SUPPORTED_IMAGE_DOCUMENT_TYPES = {
      +    ".jpg": "image/jpeg",
      +    ".jpeg": "image/jpeg",
      +    ".png": "image/png",
      +    ".webp": "image/webp",
      +    ".gif": "image/gif",
      +}
      +
      +
       def get_document_cache_dir() -> Path:
           """Return the document cache directory, creating it if it doesn't exist."""
           DOCUMENT_CACHE_DIR.mkdir(parents=True, exist_ok=True)
    • gateway/platforms/telegram.py
      @@ -76,6 +76,7 @@ class _MockContextTypes:
           resolve_proxy_url,
           SUPPORTED_VIDEO_TYPES,
           SUPPORTED_DOCUMENT_TYPES,
      +    SUPPORTED_IMAGE_DOCUMENT_TYPES,
           utf16_len,
       )
       from gateway.platforms.telegram_network import (
      @@ -4624,6 +4625,14 @@ async def _handle_media_message(self, update: Update, context: ContextTypes.DEFA
                           video_mime_to_ext = {v: k for k, v in SUPPORTED_VIDEO_TYPES.items()}
                           ext = video_mime_to_ext.get(doc.mime_type, "")
       
      +                if not ext and doc.mime_type:
      +                    # SUPPORTED_IMAGE_DOCUMENT_TYPES has duplicate values (.jpg + .jpeg
      +                    # both map to image/jpeg); keep the first ext we encounter.
      +                    image_mime_to_ext: dict[str, str] = {}
      +                    for _ext, _mime in SUPPORTED_IMAGE_DOCUMENT_TYPES.items():
      +                        image_mime_to_ext.setdefault(_mime, _ext)
      +                    ext = image_mime_to_ext.get(doc.mime_type, "")
      +
                       if ext in SUPPORTED_VIDEO_TYPES:
                           file_obj = await doc.get_file()
                           video_bytes = await file_obj.download_as_bytearray()
      @@ -4635,6 +4644,21 @@ async def _handle_media_message(self, update: Update, context: ContextTypes.DEFA
                           await self.handle_message(event)
                           return
       
      +                if ext in SUPPORTED_IMAGE_DOCUMENT_TYPES:
      +                    file_obj = await doc.get_file()
      +                    image_bytes = await file_obj.download_as_bytearray()
      +                    cached_path = cache_image_from_bytes(bytes(image_bytes), ext=ext)
      +                    event.media_urls = [cached_path]
      +                    event.media_types = [SUPPORTED_IMAGE_DOCUMENT_TYPES[ext]]
      +                    event.message_type = MessageType.PHOTO
      +                    logger.info("[Telegram] Cached user image document at %s", cached_path)
      +                    media_group_id = getattr(msg, "media_group_id", None)
      +                    if media_group_id:
      +                        await self._queue_media_group_event(str(media_group_id), event)
      +                    else:
      +                        await self.handle_message(event)
      +                    return
      +
                       # Check if supported
                       if ext not in SUPPORTED_DOCUMENT_TYPES:
                           supported_list = ", ".join(sorted(SUPPORTED_DOCUMENT_TYPES.keys()))
    • scripts/release.py
      @@ -179,6 +179,7 @@
           "[redacted:email]": "kunci115",  # PR #27098 salvage (thread-not-found retry)
           "[redacted:email]": "chromalinx",  # PR #27014 salvage (commands for groups+DM)
           "[redacted:email]": "booker1207",  # PR #25132 salvage (gate profile bots by allowed topics)
      +    "[redacted:email]": "kiranvk-2011",  # PR #24815 salvage (image documents → vision)
           "[redacted:email]": "eliteworkstation94-ai",  # PR #28157 salvage (group reply session splits)
           "[redacted:email]": "hllqkb",
           "[redacted:email]": "Bongulielmi",
    • tests/gateway/test_telegram_documents.py
      @@ -134,6 +134,11 @@ def adapter():
           a = TelegramAdapter(config)
           # Capture events instead of processing them
           a.handle_message = AsyncMock()
      +    # After PR #28494 made the empty-allowlist callback auth fail-closed
      +    # (and #28492 wired _is_callback_user_authorized into _should_process_message),
      +    # document-routing tests need to bypass the new gate so messages from fake
      +    # senders reach handle_message.
      +    a._is_callback_user_authorized = lambda user_id, **_kw: True
           return a
       
       

4 PRs closed without merging

From the close comments: done elsewhere 3 · merged via another change 1

  • #16710 fix(telegram): treat image documents (PNG/JPG/WebP/GIF) as images · done elsewhere → 77c4675, #18620, #28519
  • #20147 fix(telegram): route image documents (.webp, .png, .jpg, .gif) through image handling (#20128) · done elsewhere → 77c4675, #20128, #16710
  • #20756 fix(gateway): route image documents through vision pipeline in Telegram · done elsewhere → 77c4675, #20128
  • #24815 fix(telegram): route image documents (.png/.jpg/.webp/.gif) through vision pipeline · merged via another change → #28519
Closed issues · 2
  • Closed#18620[Bug]: Telegram incoming image files treated as document uploads
  • Closed#20128Telegram: image documents like .webp are rejected as unsupported documents instead of being routed through image handling
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.35: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
gateway
Merged PRs
#28519
Closed issues
2
Attempts
4 PRs closed without merging
Fix link checked
19 May 2026

For your agent

The same facts, typed.