{"record":{"id":"20d72f1b914268f2","repo":"zeroclaw-labs/zeroclaw","slug":"discord-send-message-with-files-failed-status","errorCode":null,"errorMessage":"Discord send message with files failed ({status}): {err}","messagePattern":"Discord send message with files failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/discord/rest.rs","lineNumber":144,"sourceCode":"            format!(\"files[{idx}]\"),\n            Part::bytes(bytes).file_name(filename),\n        );\n    }\n\n    let resp = client\n        .post(&url)\n        .header(\"Authorization\", format!(\"Bot {bot_token}\"))\n        .multipart(form)\n        .send()\n        .await?;\n\n    if !resp.status().is_success() {\n        let status = resp.status();\n        let err = resp\n            .text()\n            .await\n            .unwrap_or_else(|e| format!(\"<failed to read response body: {e}>\"));\n        anyhow::bail!(\"Discord send message with files failed ({status}): {err}\");\n    }\n\n    extract_message_id(resp).await\n}\n\nasync fn extract_message_id(resp: reqwest::Response) -> anyhow::Result<String> {\n    let body: serde_json::Value = resp.json().await?;\n    body.get(\"id\")\n        .and_then(|v| v.as_str())\n        .map(|s| s.to_string())\n        .ok_or_else(|| {\n            ::zeroclaw_log::record!(\n                WARN,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                    .with_attrs(::serde_json::json!({\"field\": \"id\"})),\n                \"discord: send response missing id field\"\n            );","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/discord/rest.rs#L126-L162","documentation":"The multipart variant of the send wrapper posts a message envelope plus file attachments; a non-2xx Discord answer makes it bail with status and body inline. File sends fail differently from plain sends: size limits and attachment counts are the usual offenders.","triggerScenarios":"POST with multipart returns non-success: 413/400 for a file over the bot's upload cap, 400 for more than 10 attachments or a zero-byte file, 403 missing Attach Files permission, 401 bad token.","commonSituations":"Agent replies attaching generated artifacts (logs, screenshots, builds) larger than the current upload cap; too many files batched into one reply; Attach Files missing on a locked-down channel.","solutions":["Check the status/body: size-related 400/413 → compress, truncate, or link the artifact instead of attaching","Cap attachments at 10 per message and split larger batches","Grant the bot Attach Files on the target channel","401 → fix the bot token"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Rust — pre-validate attachments against Discord's limits\nconst MAX_ATTACHMENTS: usize = 10;\nfn attachments_precheck(files: &[FilePayload], cap_bytes: u64) -> Result<(), String> {\n    if files.len() > MAX_ATTACHMENTS {\n        return Err(format!(\"{} attachments exceeds Discord's limit of {MAX_ATTACHMENTS}\", files.len()));\n    }\n    for f in files {\n        if f.bytes.is_empty() {\n            return Err(format!(\"attachment '{}' is empty\", f.name));\n        }\n        if f.bytes.len() as u64 > cap_bytes {\n            return Err(format!(\"attachment '{}' exceeds the upload cap; compress or link it\", f.name));\n        }\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Compress or chunk large artifacts before attaching","Split replies that would carry more than 10 files into multiple messages"],"tags":["discord","attachments","multipart","http-status","file-size"],"backgroundTag":"discord-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}