{"record":{"id":"4a008b4c3fa4ef48","repo":"zeroclaw-labs/zeroclaw","slug":"qq-send-media-message-failed-status-err","errorCode":null,"errorMessage":"QQ send media message failed ({status}): {err}","messagePattern":"QQ send media message failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":932,"sourceCode":"        let (scope, id) = Self::resolve_recipient(recipient);\n\n        let url = format!(\"{QQ_API_BASE}/v2/{scope}/{id}/messages\");\n        ensure_https(&url)?;\n\n        let body = Self::build_media_message_body(file_info, in_reply_to);\n\n        let resp = self\n            .http_client()\n            .post(&url)\n            .header(\"Authorization\", format!(\"QQBot {token}\"))\n            .json(&body)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let err = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"QQ send media message failed ({status}): {err}\");\n        }\n\n        Ok(())\n    }\n\n    /// Send a single attachment: resolve local/URL, upload, then send.\n    async fn send_attachment(\n        &self,\n        recipient: &str,\n        attachment: &QQMediaAttachment,\n        in_reply_to: Option<&str>,\n    ) -> anyhow::Result<()> {\n        let target = attachment.target.trim();\n\n        // Extract filename from target path/URL for File type display\n        let file_name = Path::new(target.split('?').next().unwrap_or(target))\n            .file_name()\n            .and_then(|n| n.to_str())","sourceCodeStart":914,"sourceCodeEnd":950,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L914-L950","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// If you reuse uploads across sends, refresh file_info before TTL expiry:\nfn upload_fresh(entry: &UploadCacheEntry, now_secs: u64) -> bool {\n    entry.expires_at > now_secs + 30 // margin before QQ's TTL\n}\n// skip cached entries that are about to expire; force re-upload instead of risking\n// a failed send_media_message","typeGuard":null,"tryCatchPattern":"match ch.send(&msg).await {\n    Err(e) if e.to_string().contains(\"QQ send media message failed\") => {\n        let s = e.to_string();\n        if s.contains(\"file_info\") || s.contains(\"expired\") {\n            // Invalidate the upload cache for this target, re-upload, retry once.\n        } else if s.contains(\"500\") || s.contains(\"502\") || s.contains(\"503\") {\n            // Transient: retry once after backoff.\n        } else {\n            // Recipient/permission problem: surface to operator, no retry.\n        }\n    }\n    rest => rest?,\n}","preventionTips":["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"],"tags":["qq","media-message","file-info","api-error","attachments"],"backgroundTag":"message-send-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}