# NousResearch/hermes-agent · Telegram image files sent as documents misrouted

## Open now

- 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.

## TL;DR

- 7 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 1 merged PR(s), 4 PR(s) closed without merging.
- A fix was merged to main in PR #28519. Verify it addresses your case.
- **Recommended:** read PR #28519 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.35`: the share of member 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.

## Closed / duplicate issues

- #18620: \[Bug\]: Telegram incoming image files treated as document uploads
- #20128: Telegram: image documents like .webp are rejected as unsupported documents instead of being routed through image handling

## Prior & competing PRs

- **#20147: fix(telegram): route image documents (.webp, .png, .jpg, .gif) through image handling (#20128)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `77c4675a50db7abbfd191d4fba4746b4f3e1559e`, `77c4675a50db7abbfd191d4fba4746b4f3e1559e`, `#20128`, `#16710`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#20756: fix(gateway): route image documents through vision pipeline in Telegram**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `77c4675a50db7abbfd191d4fba4746b4f3e1559e`, `#20128`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#28519: fix(telegram): route image documents (.png/.jpg/.webp/.gif) through vision pipeline**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `gateway/platforms/base.py`, `gateway/platforms/telegram.py`, `scripts/release.py`, `tests/gateway/test_telegram_documents.py`
  - fix hunks:
  - `gateway/platforms/base.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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 (
    ```

    ```diff
    @@ -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()
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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
     
     
    ```
- **#16710: fix(telegram): treat image documents (PNG/JPG/WebP/GIF) as images**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `77c4675a50db7abbfd191d4fba4746b4f3e1559e`, `#18620`, `#28519`
- **#24815: fix(telegram): route image documents (.png/.jpg/.webp/.gif) through vision pipeline**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#28519`

## Fix status

- Merged to main in PR #28519 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-19`.
- Subsystem: `gateway`
