zeroclaw-labs/zeroclaw · error
QQ send media message failed ({status}): {err}
Error message
QQ send media message failed ({status}): {err} What it means
After a successful upload, send_media_message POSTs the rich-media message (file_info from the upload plus the recipient) and bails with status and body on any non-2xx response. file_info from QQ has a TTL and is cached, so the classic cause is sending a file_info that expired or was invalidated; permission/scene restrictions on the media type and recipient errors are the other common causes.
Source
Thrown at crates/zeroclaw-channels/src/qq.rs:932
let (scope, id) = Self::resolve_recipient(recipient);
let url = format!("{QQ_API_BASE}/v2/{scope}/{id}/messages");
ensure_https(&url)?;
let body = Self::build_media_message_body(file_info, in_reply_to);
let resp = self
.http_client()
.post(&url)
.header("Authorization", format!("QQBot {token}"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let err = resp.text().await.unwrap_or_default();
anyhow::bail!("QQ send media message failed ({status}): {err}");
}
Ok(())
}
/// Send a single attachment: resolve local/URL, upload, then send.
async fn send_attachment(
&self,
recipient: &str,
attachment: &QQMediaAttachment,
in_reply_to: Option<&str>,
) -> anyhow::Result<()> {
let target = attachment.target.trim();
// Extract filename from target path/URL for File type display
let file_name = Path::new(target.split('?').next().unwrap_or(target))
.file_name()
.and_then(|n| n.to_str())View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the status/body: file_info-expired errors mean force a re-upload (change the file or bypass the upload cache) to get fresh file_info
- Verify the recipient openid is current and the bot has rich-media permissions for that scene
- For 5xx responses, retry the send once after a short delay
Defensive patterns
Strategy: try-catch
Validate before calling
// If you reuse uploads across sends, refresh file_info before TTL expiry:
fn upload_fresh(entry: &UploadCacheEntry, now_secs: u64) -> bool {
entry.expires_at > now_secs + 30 // margin before QQ's TTL
}
// skip cached entries that are about to expire; force re-upload instead of risking
// a failed send_media_message Try / catch
match ch.send(&msg).await {
Err(e) if e.to_string().contains("QQ send media message failed") => {
let s = e.to_string();
if s.contains("file_info") || s.contains("expired") {
// Invalidate the upload cache for this target, re-upload, retry once.
} else if s.contains("500") || s.contains("502") || s.contains("503") {
// Transient: retry once after backoff.
} else {
// Recipient/permission problem: surface to operator, no retry.
}
}
rest => rest?,
} Prevention
- Treat QQ file_info as short-lived: prefer re-upload over deep cache reuse for cron sends
- Verify recipient openids before scheduling media pushes
- Confirm the bot has rich-media permissions for the target scene (guild/group/DM) before relying on media messages
When it happens
Trigger: Reusing a cached upload past its TTL (send_attachment caches uploads to avoid re-uploading); wrong recipient openid; the msg_type/media type not permitted in the current scene or bot capability set.
Common situations: Repeated cron sends of the same attachment minutes apart; uploads surviving longer than QQ's file_info TTL; test bots lacking rich-media permissions; recipients who left the guild/group.
Related errors
- QQ upload media failed ({status}): {err}
- QQ attachment path not found: {target}
- QQ attachment too large ({} bytes, max {}): {target}
- Discord send message with files failed ({status}): {err}
- QQ channel requires the `channel-qq` feature
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/4a008b4c3fa4ef48.
Report an issue: GitHub.