hermes - ✅(Solved) Fix QQ Bot: file attachments (PDF etc.) silently dropped when download fails [3 pull requests, 1 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
NousResearch/hermes-agent#16979Fetched 2026-04-29 06:37:59
View on GitHub
Comments
0
Participants
1
Timeline
7
Reactions
0
Participants
Timeline (top)
labeled ×4cross-referenced ×3

When QQ users send file attachments (PDFs, documents, etc.), they are silently discarded by the bot adapter if the download from QQ's file CDN fails. The agent has no indication that a file was sent, and no warning appears in logs.

Error Message

Current code

try: cached_path = await self._download_and_cache(url, ct) if cached_path: other_attachments.append(f"[Attachment: {filename or ct}]") # ← no else: download returns None → attachment disappears except Exception as exc: logger.debug("[%s] Failed to cache attachment: %s", self._log_tag, exc) # ← no fallback text appended either

Root Cause

Three compounding issues in gateway/platforms/qqbot/adapter.py:

Fix Action

Fixed

PR fix notes

PR #16990: fix(qqbot): surface non-image attachments to agent when download fails (#16979)

Description (problem / solution / changelog)

Summary

QQ Bot users uploading files (PDFs, documents, videos) saw the bot silently do nothing whenever the QQ file CDN download failed: the agent never learned that an attachment had been sent, no warning was emitted at the production log level, and the user had no way to tell the upload had been dropped.

The bug

_process_attachments() in gateway/platforms/qqbot/adapter.py appended [Attachment: …] only on the success branch of the non-image / non-voice path:

else:
    # Other attachments (video, file, etc.): record as text.
    try:
        cached_path = await self._download_and_cache(url, ct)
        if cached_path:
            other_attachments.append(f\"[Attachment: {filename or ct}]\")
        # ← no else: download returns None → attachment disappears
    except Exception as exc:
        logger.debug(\"[%s] Failed to cache attachment: %s\", ...)
        # ← no fallback append either

Two real failure paths reach this code:

  1. _download_and_cache returns None — happens when _http_client is unset, the transport fails, or resp.raise_for_status() raises (the helper catches and returns None).
  2. _download_and_cache raises ValueError from the is_safe_url guard.

Both failures silently dropped the attachment and logged at DEBUG, which production deployments running at INFO/WARNING never see.

The fix

Tightly scoped to the non-image / non-voice branch:

  • Compute label = filename or ct or \"file\" once so the success and failure markers stay consistent and never collapse to an empty [Attachment: ].
  • When cached_path is None, append [Attachment: <label> (download failed)] and log at WARNING with the truncated URL.
  • When the download raises, also append the same fallback marker and log the exception at WARNING.

The image and voice branches are intentionally left alone — voice already has its own [Voice] [语音识别失败] fallback, and the image branch is outside the issue's scope.

Test plan

  • Focused regression: tests/gateway/test_qqbot.py::TestProcessAttachmentsFallback — 4 new tests cover (1) _download_and_cache returns None, (2) raises, (3) succeeds, (4) success/failure with missing filename falls back to content_type. All pass.
  • Adjacent suite: full tests/gateway/test_qqbot.py — 75 passed.
  • Regression guard: reverted gateway/platforms/qqbot/adapter.py, confirmed the 3 failure-path tests fail with empty attachment_info, restored fix, all 4 pass again.

Related

  • Fixes #16979

Changed files

  • gateway/platforms/qqbot/adapter.py (modified, +35/-3)
  • tests/gateway/test_qqbot.py (modified, +146/-0)

PR #2: fix: resolve 7 identified issues [automated]

Description (problem / solution / changelog)

Summary

This PR resolves 7 identified issues in the Hermes Agent codebase.

Issues Fixed

1. #17076 - kimi-coding vision broken (HTTP 404)

File: agent/auxiliary_client.py Problem: The kimi-coding provider was missing from _PROVIDER_VISION_MODELS, causing the vision auto-detect to fail with HTTP 404 when using image inputs. Fix: Added kimi-coding and kimi-coding-cn to _PROVIDER_VISION_MODELS dict. Commit: 82b920ff

2. #16970 - Model Picker ignores model_catalog.enabled=false

File: hermes_cli/model_switch.py Problem: The list_authenticated_providers() function ignored model_catalog.enabled=false config, showing built-in providers even when the catalog was disabled. Fix: Added catalog_enabled check that skips sections 1-3 (built-in provider enumeration) when model_catalog.enabled=false. Commit: b6b6fbbe

3. #16830 - Credential pool rotation counts toward api_max_retries

File: run_agent.py Problem: When a credential pool rotation occurred, retry_count was not reset, causing premature exhaustion of retries. Fix: Reset retry_count = 0 when _recover_with_credential_pool() succeeds. Commit: 366582d4

4. #16979 - QQ Bot file attachments silently dropped on download failure

File: gateway/platforms/qqbot/adapter.py Problem: When CDN download failed for file attachments, they were silently discarded with no indication to the agent. Fix: Added fallback text (download failed) to attachment_info when download fails. Commit: f956da73

5. #16875 - kimi-k2.6 HTTP 400 via OpenCode Go

Files: hermes_cli/setup.py, hermes_cli/models.py Problem: kimi-k2.6 was listed as an available model for opencode-go provider but returns HTTP 400 on all requests. Fix: Removed kimi-k2.6 from the opencode-go model list in both files. Commit: 5a77400d

6. #16951 - WeChat clips right-side content when scrolling Markdown tables

File: gateway/platforms/weixin.py Problem: WeChat's WebView CSS clips tables with many columns, making rightmost columns invisible. Fix: Added _truncate_wide_table_row() helper that limits table rows to 6 columns and appends ... for overflow indication. Commit: d3e43e10

7. #17009 - Termux hermes update fails with "Failed to determine Android API level"

File: hermes_cli/main.py Problem: Native extension builds (maturin) fail in Termux because ANDROID_API_LEVEL is not set. Fix: In _install_python_dependencies_with_optional_fallback(), detect Termux environment and set ANDROID_API_LEVEL=29 as a safe default. Commit: 096a80b9

Files Modified

  • agent/auxiliary_client.py (1 change)
  • hermes_cli/model_switch.py (1 change)
  • run_agent.py (1 change)
  • gateway/platforms/qqbot/adapter.py (1 change)
  • hermes_cli/setup.py (1 change)
  • hermes_cli/models.py (1 change)
  • gateway/platforms/weixin.py (2 changes)
  • hermes_cli/main.py (1 change)

Testing

All changes were validated with python3 -m py_compile to ensure no syntax errors.

Changed files

  • .gitignore (modified, +1/-0)
  • AGENTS.md (modified, +1/-1)
  • Dockerfile (modified, +6/-2)
  • acp_adapter/entry.py (modified, +11/-0)
  • acp_adapter/server.py (modified, +28/-1)
  • agent/anthropic_adapter.py (modified, +155/-78)
  • agent/auxiliary_client.py (modified, +342/-55)
  • agent/bedrock_adapter.py (modified, +41/-3)
  • agent/context_compressor.py (modified, +113/-5)
  • agent/credential_pool.py (modified, +82/-4)
  • agent/credential_sources.py (modified, +0/-1)
  • agent/error_classifier.py (modified, +32/-0)
  • agent/gemini_cloudcode_adapter.py (modified, +0/-2)
  • agent/gemini_schema.py (modified, +1/-1)
  • agent/google_code_assist.py (modified, +0/-1)
  • agent/google_oauth.py (modified, +3/-3)
  • agent/image_routing.py (added, +236/-0)
  • agent/memory_manager.py (modified, +113/-5)
  • agent/model_metadata.py (modified, +56/-21)
  • agent/nous_rate_guard.py (modified, +144/-1)
  • agent/onboarding.py (added, +191/-0)
  • agent/prompt_builder.py (modified, +38/-0)
  • agent/redact.py (modified, +7/-3)
  • agent/shell_hooks.py (modified, +7/-2)
  • agent/skill_commands.py (modified, +2/-2)
  • agent/title_generator.py (modified, +39/-5)
  • agent/transports/anthropic.py (modified, +1/-7)
  • agent/transports/chat_completions.py (modified, +74/-0)
  • agent/transports/codex.py (modified, +1/-3)
  • cli-config.yaml.example (modified, +28/-8)
  • cli.py (modified, +527/-196)
  • cron/jobs.py (modified, +34/-5)
  • cron/scheduler.py (modified, +39/-5)
  • docker/entrypoint.sh (modified, +9/-7)
  • flake.nix (modified, +1/-0)
  • gateway/builtin_hooks/boot_md.py (removed, +0/-85)
  • gateway/channel_directory.py (modified, +67/-14)
  • gateway/config.py (modified, +84/-3)
  • gateway/display_config.py (modified, +3/-1)
  • gateway/hooks.py (modified, +7/-13)
  • gateway/mirror.py (modified, +57/-11)
  • gateway/pairing.py (modified, +2/-1)
  • gateway/platforms/__init__.py (modified, +2/-0)
  • gateway/platforms/base.py (modified, +233/-16)
  • gateway/platforms/discord.py (modified, +18/-24)
  • gateway/platforms/email.py (modified, +3/-0)
  • gateway/platforms/feishu_comment.py (modified, +0/-1)
  • gateway/platforms/helpers.py (modified, +11/-2)
  • gateway/platforms/matrix.py (modified, +493/-47)
  • gateway/platforms/mattermost.py (modified, +0/-1)
  • gateway/platforms/qqbot/adapter.py (modified, +7/-7)
  • gateway/platforms/slack.py (modified, +753/-70)
  • gateway/platforms/telegram.py (modified, +138/-14)
  • gateway/platforms/weixin.py (modified, +47/-3)
  • gateway/platforms/yuanbao.py (added, +4754/-0)
  • gateway/platforms/yuanbao_media.py (added, +645/-0)
  • gateway/platforms/yuanbao_proto.py (added, +1209/-0)
  • gateway/platforms/yuanbao_sticker.py (added, +558/-0)
  • gateway/run.py (modified, +1139/-279)
  • gateway/runtime_footer.py (added, +150/-0)
  • gateway/session.py (modified, +16/-21)
  • gateway/stream_consumer.py (modified, +110/-0)
  • gateway/whatsapp_identity.py (modified, +21/-1)
  • hermes_cli/auth.py (modified, +40/-4)
  • hermes_cli/azure_detect.py (modified, +1/-1)
  • hermes_cli/backup.py (modified, +272/-1)
  • hermes_cli/banner.py (modified, +0/-1)
  • hermes_cli/claw.py (modified, +67/-6)
  • hermes_cli/commands.py (modified, +116/-5)
  • hermes_cli/config.py (modified, +322/-29)
  • hermes_cli/debug.py (modified, +13/-7)
  • hermes_cli/dingtalk_auth.py (modified, +0/-1)
  • hermes_cli/doctor.py (modified, +11/-1)
  • hermes_cli/env_loader.py (modified, +2/-1)
  • hermes_cli/fallback_cmd.py (added, +361/-0)
  • hermes_cli/gateway.py (modified, +25/-4)
  • hermes_cli/hooks.py (modified, +1/-2)
  • hermes_cli/main.py (modified, +689/-58)
  • hermes_cli/model_catalog.py (added, +329/-0)
  • hermes_cli/model_switch.py (modified, +366/-306)
  • hermes_cli/models.py (modified, +251/-44)
  • hermes_cli/nous_subscription.py (modified, +16/-8)
  • hermes_cli/oneshot.py (modified, +28/-11)
  • hermes_cli/platforms.py (modified, +1/-0)
  • hermes_cli/plugins.py (modified, +14/-0)
  • hermes_cli/plugins_cmd.py (modified, +0/-1)
  • hermes_cli/profiles.py (modified, +58/-2)
  • hermes_cli/providers.py (modified, +26/-0)
  • hermes_cli/runtime_provider.py (modified, +100/-14)
  • hermes_cli/setup.py (modified, +70/-17)
  • hermes_cli/skills_hub.py (modified, +230/-20)
  • hermes_cli/slack_cli.py (added, +152/-0)
  • hermes_cli/status.py (modified, +3/-2)
  • hermes_cli/timeouts.py (modified, +4/-4)
  • hermes_cli/tips.py (modified, +2/-4)
  • hermes_cli/tools_config.py (modified, +173/-4)
  • hermes_cli/web_server.py (modified, +11/-14)
  • hermes_cli/webhook.py (modified, +2/-2)
  • hermes_logging.py (modified, +3/-4)
  • hermes_state.py (modified, +578/-164)

PR #17108: fix: resolve 7 identified issues [automated]

Description (problem / solution / changelog)

Summary

This PR resolves 7 identified issues in the Hermes Agent codebase. All issues were identified from open GitHub issues in NousResearch/hermes-agent.

Issues Fixed

1. #17076 - kimi-coding vision broken (HTTP 404)

File: agent/auxiliary_client.py Problem: The kimi-coding provider was missing from _PROVIDER_VISION_MODELS, causing the vision auto-detect to fail with HTTP 404 when using image inputs. Fix: Added kimi-coding and kimi-coding-cn to _PROVIDER_VISION_MODELS dict. Commit: 82b920ff

2. #16970 - Model Picker ignores model_catalog.enabled=false

File: hermes_cli/model_switch.py Problem: The list_authenticated_providers() function ignored model_catalog.enabled=false config, showing built-in providers even when the catalog was disabled. Fix: Added catalog_enabled check that skips sections 1-3 (built-in provider enumeration) when model_catalog.enabled=false. Commit: b6b6fbbe

3. #16830 - Credential pool rotation counts toward api_max_retries

File: run_agent.py Problem: When a credential pool rotation occurred, retry_count was not reset, causing premature exhaustion of retries. Fix: Reset retry_count = 0 when _recover_with_credential_pool() succeeds. Commit: 366582d4

4. #16979 - QQ Bot file attachments silently dropped on download failure

File: gateway/platforms/qqbot/adapter.py Problem: When CDN download failed for file attachments, they were silently discarded with no indication to the agent. Fix: Added fallback text (download failed) to attachment_info when download fails. Commit: f956da73

5. #16875 - kimi-k2.6 HTTP 400 via OpenCode Go

Files: hermes_cli/setup.py, hermes_cli/models.py Problem: kimi-k2.6 was listed as an available model for opencode-go provider but returns HTTP 400 on all requests. Fix: Removed kimi-k2.6 from the opencode-go model list in both files. Commit: 5a77400d

6. #16951 - WeChat clips right-side content when scrolling Markdown tables

File: gateway/platforms/weixin.py Problem: WeChat's WebView CSS clips tables with many columns, making rightmost columns invisible. Fix: Added _truncate_wide_table_row() helper that limits table rows to 6 columns and appends ... for overflow indication. Commit: d3e43e10

7. #17009 - Termux hermes update fails with "Failed to determine Android API level"

File: hermes_cli/main.py Problem: Native extension builds (maturin) fail in Termux because ANDROID_API_LEVEL is not set. Fix: In _install_python_dependencies_with_optional_fallback(), detect Termux environment and set ANDROID_API_LEVEL=29 as a safe default. Commit: 096a80b9

Files Modified

  • agent/auxiliary_client.py
  • hermes_cli/model_switch.py
  • run_agent.py
  • gateway/platforms/qqbot/adapter.py
  • hermes_cli/setup.py
  • hermes_cli/models.py
  • gateway/platforms/weixin.py
  • hermes_cli/main.py

Commits (7 total)

  • 82b920ff - fix(vision): add kimi-coding provider to _PROVIDER_VISION_MODELS
  • b6b6fbbe - fix(model_picker): respect model_catalog.enabled=false config
  • 366582d4 - fix(credential_pool): reset retry_count on credential rotation
  • f956da73 - fix(qqbot): add fallback text when attachment download fails
  • 5a77400d - fix(setup): remove kimi-k2.6 from opencode-go model list
  • d3e43e10 - fix(weixin): truncate wide tables to prevent viewport clipping
  • 096a80b9 - fix(termux): set ANDROID_API_LEVEL default during update

Changed files

  • agent/auxiliary_client.py (modified, +4/-0)
  • gateway/platforms/qqbot/adapter.py (modified, +5/-0)
  • gateway/platforms/weixin.py (modified, +21/-0)
  • hermes_cli/main.py (modified, +9/-0)
  • hermes_cli/model_switch.py (modified, +355/-344)
  • hermes_cli/models.py (modified, +0/-1)
  • hermes_cli/setup.py (modified, +1/-1)
  • run_agent.py (modified, +1/-0)

Code Example

# Current code
try:
    cached_path = await self._download_and_cache(url, ct)
    if cached_path:
        other_attachments.append(f"[Attachment: {filename or ct}]")
    # ← no else: download returns None → attachment disappears
except Exception as exc:
    logger.debug("[%s] Failed to cache attachment: %s", self._log_tag, exc)
    # ← no fallback text appended either

---

@@ -1190,11 +1190,14 @@ class QQAdapter(BasePlatformAdapter):
             else:
                 # Other attachments (video, file, etc.): record as text.
                 try:
-                    cached_path = await self._download_and_cache(url, ct)
+                    cached_path = await self._download_and_cache(url, ct, original_filename=filename)
                     if cached_path:
                         other_attachments.append(f"[Attachment: {filename or ct}]")
+                    else:
+                        other_attachments.append(f"[Attachment download failed: {filename or ct}]")
                 except Exception as exc:
-                    logger.debug("[%s] Failed to cache attachment: %s", self._log_tag, exc)
+                    logger.warning("[%s] Failed to cache attachment: %s", self._log_tag, exc)
+                    other_attachments.append(f"[Attachment download failed: {filename or ct}]")

@@ -1204,7 +1207,7 @@ class QQAdapter(BasePlatformAdapter):
             "attachment_info": attachment_info,
         }

-    async def _download_and_cache(self, url: str, content_type: str) -> Optional[str]:
+    async def _download_and_cache(self, url: str, content_type: str, original_filename: str = "") -> Optional[str]:
         """Download a URL and cache it locally."""

@@ -1218,12 +1221,12 @@ class QQAdapter(BasePlatformAdapter):
             resp = await self._http_client.get(
                 url,
                 timeout=30.0,
-                headers=self._qq_media_headers(),
+                headers=self._qq_media_headers(url),
             )
             resp.raise_for_status()
             data = resp.content
         except Exception as exc:
-            logger.debug(
+            logger.warning(
                 "[%s] Download failed for %s: %s", self._log_tag, url[:80], exc
             )
             return None

@@ -1236,7 +1239,7 @@ class QQAdapter(BasePlatformAdapter):
             # Convert to .wav using ffmpeg so STT engines can process it.
             return await self._convert_audio_to_wav(data, url)
         else:
-            filename = Path(urlparse(url).path).name or "qq_attachment"
+            filename = original_filename or Path(urlparse(url).path).name or "qq_attachment"
             return cache_document_from_bytes(data, filename)

@@ -1261,13 +1264,15 @@ class QQAdapter(BasePlatformAdapter):
             return True
         return False

-    def _qq_media_headers(self) -> Dict[str, str]:
+    def _qq_media_headers(self, url: str = "") -> Dict[str, str]:
         """Return Authorization headers for QQ multimedia CDN downloads.

         QQ multimedia URLs (multimedia.nt.qq.com.cn) require the bot's
         access token in an Authorization header, otherwise the download
         returns a non-200 status.
         """
+        if url and "grouptalk.c2c.qq.com" in url:
+            logger.info("[%s] File CDN download request: host=%s", self._log_tag, urlparse(url).hostname)
         if self._access_token:
             return {"Authorization": f"QQBot {self._access_token}"}
         return {}
RAW_BUFFERClick to expand / collapse

Summary

When QQ users send file attachments (PDFs, documents, etc.), they are silently discarded by the bot adapter if the download from QQ's file CDN fails. The agent has no indication that a file was sent, and no warning appears in logs.

Root Cause

Three compounding issues in gateway/platforms/qqbot/adapter.py:

1. No fallback text on download failure (silent data loss)

The else branch in _process_attachments() (non-image, non-voice attachments) only appends to other_attachments when cached_path is truthy — there is no else branch and the except block silently swallows the error:

# Current code
try:
    cached_path = await self._download_and_cache(url, ct)
    if cached_path:
        other_attachments.append(f"[Attachment: {filename or ct}]")
    # ← no else: download returns None → attachment disappears
except Exception as exc:
    logger.debug("[%s] Failed to cache attachment: %s", self._log_tag, exc)
    # ← no fallback text appended either

2. Debug-level logging invisible in production

Both failure paths use logger.debug():

  • _download_and_cache() download exception
  • _process_attachments() attachment cache exception

Production log level is typically INFO or WARNING, so these messages are never seen.

3. QQ file CDN (grouptalk.c2c.qq.com) auth mismatch

QQ uses different CDNs with different auth requirements:

  • Image CDN (multimedia.nt.qq.com.cn): requires Authorization: QQBot {token} header — current code handles this correctly
  • File CDN (grouptalk.c2c.qq.com): uses URL-embedded signatures (sign=, sig= params), auth header behavior is unclear/different — current code sends the same Authorization header unconditionally

The _qq_media_headers() method does not accept the URL, so it cannot differentiate between CDNs. File CDN downloads likely fail due to auth mismatch, but there is no logging to diagnose this.

4. Original filename lost

_download_and_cache() extracts filename from the CDN URL path (e.g., qqdownloadftnv5), which is a non-semantic CDN path segment. The actual filename from QQ message metadata (attachment.filename, e.g., 报告.pdf) is available in _process_attachments but never passed through.

Proposed Fix

All changes confined to gateway/platforms/qqbot/adapter.py:

  1. Add fallback text on failure — append [Attachment download failed: {filename}] in both the else and except branches so the agent can see a file was sent
  2. Upgrade logger.debuglogger.warning at both failure sites
  3. Pass original_filename through to _download_and_cache(), use it with priority over URL-path extraction
  4. Add diagnostic logging for file CDN requests — pass URL to _qq_media_headers(), log info when grouptalk.c2c.qq.com is detected

Diff

@@ -1190,11 +1190,14 @@ class QQAdapter(BasePlatformAdapter):
             else:
                 # Other attachments (video, file, etc.): record as text.
                 try:
-                    cached_path = await self._download_and_cache(url, ct)
+                    cached_path = await self._download_and_cache(url, ct, original_filename=filename)
                     if cached_path:
                         other_attachments.append(f"[Attachment: {filename or ct}]")
+                    else:
+                        other_attachments.append(f"[Attachment download failed: {filename or ct}]")
                 except Exception as exc:
-                    logger.debug("[%s] Failed to cache attachment: %s", self._log_tag, exc)
+                    logger.warning("[%s] Failed to cache attachment: %s", self._log_tag, exc)
+                    other_attachments.append(f"[Attachment download failed: {filename or ct}]")

@@ -1204,7 +1207,7 @@ class QQAdapter(BasePlatformAdapter):
             "attachment_info": attachment_info,
         }

-    async def _download_and_cache(self, url: str, content_type: str) -> Optional[str]:
+    async def _download_and_cache(self, url: str, content_type: str, original_filename: str = "") -> Optional[str]:
         """Download a URL and cache it locally."""

@@ -1218,12 +1221,12 @@ class QQAdapter(BasePlatformAdapter):
             resp = await self._http_client.get(
                 url,
                 timeout=30.0,
-                headers=self._qq_media_headers(),
+                headers=self._qq_media_headers(url),
             )
             resp.raise_for_status()
             data = resp.content
         except Exception as exc:
-            logger.debug(
+            logger.warning(
                 "[%s] Download failed for %s: %s", self._log_tag, url[:80], exc
             )
             return None

@@ -1236,7 +1239,7 @@ class QQAdapter(BasePlatformAdapter):
             # Convert to .wav using ffmpeg so STT engines can process it.
             return await self._convert_audio_to_wav(data, url)
         else:
-            filename = Path(urlparse(url).path).name or "qq_attachment"
+            filename = original_filename or Path(urlparse(url).path).name or "qq_attachment"
             return cache_document_from_bytes(data, filename)

@@ -1261,13 +1264,15 @@ class QQAdapter(BasePlatformAdapter):
             return True
         return False

-    def _qq_media_headers(self) -> Dict[str, str]:
+    def _qq_media_headers(self, url: str = "") -> Dict[str, str]:
         """Return Authorization headers for QQ multimedia CDN downloads.

         QQ multimedia URLs (multimedia.nt.qq.com.cn) require the bot's
         access token in an Authorization header, otherwise the download
         returns a non-200 status.
         """
+        if url and "grouptalk.c2c.qq.com" in url:
+            logger.info("[%s] File CDN download request: host=%s", self._log_tag, urlparse(url).hostname)
         if self._access_token:
             return {"Authorization": f"QQBot {self._access_token}"}
         return {}

Open Follow-up

The actual file CDN auth for grouptalk.c2c.qq.com needs investigation with a live QQ Bot instance (packet capture to determine required headers/cookies/signature handling). The diagnostic logging added here will help with that investigation.

Impact

Users sending PDFs or other files to the QQ bot receive no acknowledgment — the agent behaves as if nothing was sent. This is a data loss bug from the user's perspective.

extent analysis

TL;DR

To fix the issue of silently discarded file attachments sent by QQ users, update the gateway/platforms/qqbot/adapter.py file to include fallback text on download failure, upgrade logging levels, pass original filenames, and add diagnostic logging for file CDN requests.

Guidance

  • Update the _process_attachments method to append a fallback text ([Attachment download failed: {filename}]) when the download fails, ensuring the agent is aware a file was sent.
  • Change logger.debug to logger.warning in both failure paths to increase log visibility in production environments.
  • Modify the _download_and_cache method to accept and prioritize the original filename from the QQ message metadata.
  • Enhance the _qq_media_headers method to log diagnostic information when a file CDN request is detected, aiding in the investigation of the actual authentication requirements for grouptalk.c2c.qq.com.

Example

The proposed fix includes a diff that demonstrates these changes, such as modifying the _download_and_cache method to accept an original_filename parameter and updating the logging level in the except block:

async def _download_and_cache(self, url: str, content_type: str, original_filename: str = "") -> Optional[str]:
    try:
        # ...
    except Exception as exc:
        logger.warning("[%s] Failed to cache attachment: %s", self._log_tag, exc)
        other_attachments.append(f"[Attachment download failed: {filename or ct}]")

Notes

The actual authentication mechanism for the grouptalk.c2c.qq.com CDN is not fully understood and requires further investigation with a live QQ Bot instance to determine the necessary headers, cookies, or signature handling.

Recommendation

Apply the proposed workaround by updating the gateway/platforms/qqbot/adapter.py file as described, to immediately address the silent data loss issue and provide better logging for future diagnostics.

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

hermes - ✅(Solved) Fix QQ Bot: file attachments (PDF etc.) silently dropped when download fails [3 pull requests, 1 participants]