zeroclaw-labs/zeroclaw · error
QQ upload media failed ({status}): {err}
Error message
QQ upload media failed ({status}): {err} What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/qq.rs:853
// QQ API uses file_name for File type to display the filename in chat
if file_type == QQMediaFileType::File
&& let Some(name) = file_name
{
body["file_name"] = json!(name);
}
let resp = self
.http_client()
.post(&api_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 upload media failed ({status}): {err}");
}
let upload_resp: QQUploadResponse = resp.json().await?;
Ok((upload_resp.file_info, upload_resp.ttl))
}
/// Build the request body for a markdown text message (msg_type=2).
///
/// Pure function — no I/O, no token fetch, no HTTP. Extracted from
/// `send_text_markdown` so the body shape (including the optional
/// `msg_id` for passive group replies) can be asserted directly in
/// tests.
fn build_text_markdown_body(content: &str, in_reply_to: Option<&str>) -> serde_json::Value {
let mut body = json!({
"markdown": {
"content": content,
},
"msg_type": 2,View on GitHub (pinned to 88bb9c8533)
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
Defensive patterns
Strategy: retry
Validate before calling
// Pre-validate the local file exactly like the channel does:
async fn qq_uploadable(path: &Path) -> anyhow::Result<()> {
anyhow::ensure!(path.exists(), "attachment missing: {}", path.display());
let len = tokio::fs::metadata(path).await?.len();
anyhow::ensure!(len <= 10 * 1024 * 1024, "attachment {} bytes exceeds 10 MiB", len);
Ok(())
} Try / catch
match ch.send(&msg).await {
Err(e) if e.to_string().contains("QQ upload media failed") => {
let s = e.to_string();
if s.contains("4") && !s.contains("5") {
// 4xx: media type mismatch or auth — inspect body, fix marker/creds; do not blind-retry
} else {
// 5xx: transient platform failure — retry once after a short delay
}
}
rest => rest?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- QQ send media message 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/93cce10f1a5fbeb0.
Report an issue: GitHub.