fix(qqbot): surface non-image attachments to agent when download fails (#16979)#16990
fix(qqbot): surface non-image attachments to agent when download fails (#16979)#16990briandevans wants to merge 2 commits into
Conversation
NousResearch#16979) QQ users sending file/video/document attachments saw the bot silently do nothing whenever the CDN download failed. `_process_attachments` only appended a `[Attachment: …]` marker on the success branch — when `_download_and_cache` returned `None` (no `_http_client`, transport error, content-type mismatch) or raised (unsafe URL), the attachment disappeared without any signal to the agent. Both failure paths logged at `DEBUG`, so production deployments running at INFO/WARNING never even saw the error. This makes the failure observable without expanding scope: - Always append `[Attachment: <name> (download failed)]` when the download returns no payload or raises, so the agent at least knows a file was sent. - Upgrade both failure logs to `WARNING` so production logs surface the CDN error. - Reuse a single `label = filename or ct or "file"` so the success and failure markers stay consistent and never collapse to an empty `[Attachment: ]`. Image and voice paths are intentionally untouched — voice already has its own `[Voice] [语音识别失败]` fallback, and the image branch is out of scope for this issue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes QQBot behavior where non-image/non-voice attachments (e.g., PDFs, videos) were silently dropped when CDN download/caching fails, ensuring the agent still receives an attachment marker and operators get higher-signal logs.
Changes:
- Add a consistent
labeland append a fallback[Attachment: … (download failed)]marker when caching returnsNoneor raises. - Promote attachment download/cache failures to
WARNINGlogs in the non-image/non-voice path. - Add focused regression tests covering success/failure cases and filename/content-type label fallback.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
gateway/platforms/qqbot/adapter.py |
Ensures non-image/non-voice attachments always surface to the agent with a failure marker and emits warning logs on failure. |
tests/gateway/test_qqbot.py |
Adds regression tests verifying attachment markers appear on failure and omit the failure marker on success. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| label = filename or ct or "file" | ||
| try: |
There was a problem hiding this comment.
label = filename or ct or "file" can still produce an effectively empty label when filename is whitespace (e.g., " "), since filename isn't stripped. Consider computing label from filename.strip() (and possibly normalizing newlines) so the attachment marker never becomes [Attachment: (download failed)].
| logger.warning( | ||
| "[%s] Attachment download returned no payload: %s (%s)", | ||
| self._log_tag, | ||
| label, | ||
| url[:80], | ||
| ) |
There was a problem hiding this comment.
The new WARNING log includes url[:80], which for QQ file CDN links often contains signed query parameters (sign=..., sig=...). Emitting those at WARNING increases the chance of leaking download-capable tokens into production logs. Consider logging only urlparse(url).hostname + path (omit query), or explicitly redact known signature parameters before logging.
| other_attachments.append(f"[Attachment: {label}]") | ||
| else: | ||
| logger.warning( | ||
| "[%s] Attachment download returned no payload: %s (%s)", |
There was a problem hiding this comment.
The message "Attachment download returned no payload" is misleading because _download_and_cache() returns None for multiple reasons (e.g., HTTP client not initialized, network/HTTP errors that are caught and turned into None). Consider rewording this to a generic "Attachment download failed (no cached path)" or adjusting _download_and_cache to provide/propagate a failure reason so ops can distinguish misconfiguration vs CDN failures.
| "[%s] Attachment download returned no payload: %s (%s)", | |
| "[%s] Attachment download failed (no cached path): %s (%s)", |
Address Copilot review on PR NousResearch#16990: **Finding 1 (line 1196 — whitespace filename slipped past fallback):** ``label = filename or ct or "file"`` would treat ``" "`` as truthy and produce ``[Attachment: (download failed)]``. Strip both the filename and the content-type before the falsy chain so the label always carries something meaningful. **Finding 2 (line 1206 — signed query params leaked into WARNING log):** QQ file CDN URLs are signed (``sign=``, ``sig=``). Logging ``url[:80]`` risked emitting download-capable tokens to production logs. Replace with ``urlparse(...).hostname + path`` so the log keeps debuggability (host tells you which CDN, path tells you which endpoint) without the secret bearer. **Finding 3 (line 1202 — misleading "no payload" message):** ``_download_and_cache()`` returns ``None`` for many reasons (HTTP client not initialized, transient network error, empty response …); "no payload" implies a successful empty body, which is one specific case. Reword to "(no cached path)" — accurate regardless of which branch returned None. Two new regression tests: * ``test_whitespace_filename_falls_back_to_content_type`` — asserts a whitespace-only filename does not produce ``Attachment: `` (double space) and that the content-type fallback fires. * ``test_failure_log_does_not_leak_signed_query_params`` — asserts the WARNING record never contains ``sign=`` or the signature value, but still contains the host + path. 77/77 tests/gateway/test_qqbot.py pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@copilot All three findings addressed in commit Finding 1 (line 1196 — whitespace filename): Real bug. Finding 2 (line 1206 — signed query params in WARNING log): Real risk. QQ file CDN URLs carry Finding 3 (line 1202 — misleading "no payload"): Real ambiguity. Two new regression tests in
77/77 |
|
CI audit — Touched code (qqbot adapter Sample baselines reproduced on clean
This PR's touched-code regression suite ( |
|
Closing to keep the queue clean — happy to reopen if this is still useful. |
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()ingateway/platforms/qqbot/adapter.pyappended
[Attachment: …]only on the success branch of thenon-image / non-voice path:
Two real failure paths reach this code:
_download_and_cachereturnsNone— happens when_http_clientis unset, the transport fails, or
resp.raise_for_status()raises(the helper catches and returns
None)._download_and_cacheraisesValueErrorfrom theis_safe_urlguard.Both failures silently dropped the attachment and logged at
DEBUG,which production deployments running at
INFO/WARNINGnever see.The fix
Tightly scoped to the non-image / non-voice branch:
label = filename or ct or \"file\"once so the success andfailure markers stay consistent and never collapse to an empty
[Attachment: ].cached_pathisNone, append[Attachment: <label> (download failed)]and log atWARNINGwith the truncated URL.log the exception at
WARNING.The image and voice branches are intentionally left alone — voice
already has its own
[Voice] [语音识别失败]fallback, and the imagebranch is outside the issue's scope.
Test plan
tests/gateway/test_qqbot.py::TestProcessAttachmentsFallback— 4 new tests cover (1)
_download_and_cachereturns None, (2)raises, (3) succeeds, (4) success/failure with missing filename
falls back to content_type. All pass.
tests/gateway/test_qqbot.py— 75 passed.gateway/platforms/qqbot/adapter.py,confirmed the 3 failure-path tests fail with empty
attachment_info, restored fix, all 4 pass again.Related