zeroclaw-labs/zeroclaw · error · anyhow::Error
attachment exceeds {} MB limit
Error message
attachment exceeds {} MB limit What it means
After fully reading the response body (`resp.bytes()`), the actual byte count is checked against `WECHAT_MEDIA_MAX_BYTES` (100 MiB). This bail fires when the downloaded size exceeds the limit even though the `Content-Length` pre-check did not catch it — i.e. the header was absent (chunked transfer) or understated the real size. It is the enforcement backstop for error 335.
Source
Thrown at crates/zeroclaw-channels/src/wechat.rs:1206
if let Some(len) = resp.content_length()
&& len > WECHAT_MEDIA_MAX_BYTES
{
anyhow::bail!(
"attachment Content-Length ({len} bytes) exceeds {} MB limit",
WECHAT_MEDIA_MAX_BYTES / (1024 * 1024)
);
}
let content_type = resp
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.map(str::to_string);
let bytes = resp.bytes().await?.to_vec();
if bytes.len() as u64 > WECHAT_MEDIA_MAX_BYTES {
anyhow::bail!(
"attachment exceeds {} MB limit",
WECHAT_MEDIA_MAX_BYTES / (1024 * 1024)
);
}
Ok(WeChatMediaPayload {
file_name: self.remote_file_name(url, content_type.as_deref(), kind),
bytes,
})
}
async fn load_attachment_payload(
&self,
attachment: &WeChatAttachment,
) -> anyhow::Result<WeChatMediaPayload> {
let target = attachment.target.trim();
if is_remote_url(target) {
return selfView on GitHub (pinned to 88bb9c8533)
Solutions
- Same as the Content-Length case: produce a file under 100 MiB (compress/trim) or share an external link in the message body instead of attaching.
- Check with `curl -sI <url>` (and `curl -s <url> | wc -c`) whether the header is missing or wrong, and fix the hosting to declare accurate sizes.
- If you control the server, set an accurate Content-Length so the early check (error 335) aborts before the full download instead of after.
Example fix
// before: attach an oversized dynamically-generated file
let target = format!("https://exports.example.com/report/{id}?format=zip"); // >100 MiB, no Content-Length
channel.send(msg_with_attachment(&target)).await?; // downloads all bytes, then bails
// after: probe size cheaply first, then choose link vs attachment
let size = probe_size(&target).await?; // HEAD, or stream-and-count early bytes
if size > 100 * 1024 * 1024 {
channel.send(msg_with_text(&format!("Report too large, download: {target}"))).await?;
} else {
channel.send(msg_with_attachment(&target)).await?;
} Defensive patterns
Strategy: validation
Validate before calling
// stream-count when no trustworthy Content-Length exists
async fn actual_size_at_most(client: &reqwest::Client, url: &str, max: u64) -> anyhow::Result<bool> {
use futures_util::StreamExt;
let resp = client.get(url).send().await?.error_for_status()?;
let mut stream = resp.bytes_stream();
let mut total = 0u64;
while let Some(chunk) = stream.next().await {
total += chunk?.len() as u64;
if total > max { return Ok(false); } // abort early
}
Ok(true)
} Type guard
fn within_wechat_limit(len: u64) -> bool { len <= 100 * 1024 * 1024 } Try / catch
match channel.send(msg_with_attachment(&url)).await {
Err(err) if err.to_string().contains("exceeds 100 MB limit") => {
let share = compress_or_rehost(&url).await?;
channel.send(msg_with_text(&format!("Large file: {share}"))).await?;
}
other => other?,
} Prevention
- Do not trust Content-Length alone; when the header is absent, stream-abort once the running total passes 100 MiB.
- Prefer origins that declare accurate Content-Length so the cheap pre-check (error 335) fires instead of a full wasted download.
- Generate attachment files/exports with a hard size budget at creation time.
When it happens
Trigger: A 2xx attachment download served with `Transfer-Encoding: chunked` and no `Content-Length`, or with a lying/smaller Content-Length, whose actual body exceeds 100 MiB. Reached via `send` with an oversized `https://` attachment target; also possible when a proxy re-chunks an upstream response.
Common situations: Dynamic endpoints that stream generated archives/videos without knowing the size ahead; misconfigured servers sending wrong Content-Length; proxies (some CDNs, ALBs) stripping Content-Length and switching to chunked; compressed-at-origin responses that decompress larger on the wire path. Same real-world content as 335: recordings, exports, datasets over 100 MiB.
Related errors
- attachment Content-Length ({len} bytes) exceeds {} MB limit
- refusing non-HTTPS attachment URL: {url}
- attachment download failed ({status}): {body}
- inbound attachment exceeds {} MB limit
- matrix: marker URL {url} exceeded {MAX_MARKER_BYTES}-byte ca
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/d20afee00e155a53.
Report an issue: GitHub.