{"record":{"id":"93cce10f1a5fbeb0","repo":"zeroclaw-labs/zeroclaw","slug":"qq-upload-media-failed-status-err","errorCode":null,"errorMessage":"QQ upload media failed ({status}): {err}","messagePattern":"QQ upload media failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":853,"sourceCode":"        // QQ API uses file_name for File type to display the filename in chat\n        if file_type == QQMediaFileType::File\n            && let Some(name) = file_name\n        {\n            body[\"file_name\"] = json!(name);\n        }\n\n        let resp = self\n            .http_client()\n            .post(&api_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 upload media failed ({status}): {err}\");\n        }\n\n        let upload_resp: QQUploadResponse = resp.json().await?;\n        Ok((upload_resp.file_info, upload_resp.ttl))\n    }\n\n    /// Build the request body for a markdown text message (msg_type=2).\n    ///\n    /// Pure function — no I/O, no token fetch, no HTTP. Extracted from\n    /// `send_text_markdown` so the body shape (including the optional\n    /// `msg_id` for passive group replies) can be asserted directly in\n    /// tests.\n    fn build_text_markdown_body(content: &str, in_reply_to: Option<&str>) -> serde_json::Value {\n        let mut body = json!({\n            \"markdown\": {\n                \"content\": content,\n            },\n            \"msg_type\": 2,","sourceCodeStart":835,"sourceCodeEnd":871,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L835-L871","documentation":"upload_media POSTs the file bytes to QQ's rich-media upload endpoint and bails with status and body on any non-2xx response. It runs after ensure_https and the local 10 MiB size check, so this error means QQ itself rejected the upload — commonly a token/permission problem, a media type inconsistent with the declared file type, or a platform-side failure.","triggerScenarios":"send_attachment uploading a local file whose declared marker type ([IMAGE:] / [VIDEO:] / [AUDIO:] / [DOCUMENT:]) does not match the actual content; access token expired/invalid at upload time; QQ-side errors (5xx) or media quotas.","commonSituations":"Audio sent as [DOCUMENT:] after transcription provider changes; mislabeled attachments from agent output; uploads close to platform quotas during bursts; stale tokens on long-lived processes.","solutions":["Read the status and body in the message — 4xx with token errors means re-auth/restart the channel; other 4xx usually means the declared media type mismatches the bytes, fix the marker","Verify the file is a valid, non-corrupt media file under 10 MiB and that the marker type matches the content","For 5xx bodies, retry the send once after a short delay; QQ media endpoints have transient failures"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-validate the local file exactly like the channel does:\nasync fn qq_uploadable(path: &Path) -> anyhow::Result<()> {\n    anyhow::ensure!(path.exists(), \"attachment missing: {}\", path.display());\n    let len = tokio::fs::metadata(path).await?.len();\n    anyhow::ensure!(len <= 10 * 1024 * 1024, \"attachment {} bytes exceeds 10 MiB\", len);\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match ch.send(&msg).await {\n    Err(e) if e.to_string().contains(\"QQ upload media failed\") => {\n        let s = e.to_string();\n        if s.contains(\"4\") && !s.contains(\"5\") {\n            // 4xx: media type mismatch or auth — inspect body, fix marker/creds; do not blind-retry\n        } else {\n            // 5xx: transient platform failure — retry once after a short delay\n        }\n    }\n    rest => rest?,\n}","preventionTips":["Keep marker types ([IMAGE:]/[VIDEO:]/[AUDIO:]/[DOCUMENT:]) consistent with actual file content","Validate size and readability before the send step in attachment-producing pipelines","Log the QQ response body — it names the exact platform error code"],"tags":["qq","media-upload","attachments","api-error"],"backgroundTag":"media-upload-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}