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

  1. 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
  2. Verify the recipient openid is current and the bot has rich-media permissions for that scene
  3. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/4a008b4c3fa4ef48. Report an issue: GitHub.