{"record":{"id":"839b3bf6e9c7da3e","repo":"zeroclaw-labs/zeroclaw","slug":"attachment-content-length-len-bytes-exceeds","errorCode":null,"errorMessage":"attachment Content-Length ({len} bytes) exceeds {} MB limit","messagePattern":"attachment Content-Length \\((.+?) bytes\\) exceeds (.+?) MB limit","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wechat.rs","lineNumber":1192,"sourceCode":"        }\n        let resp = self\n            .client\n            .get(url)\n            .timeout(API_TIMEOUT)\n            .send()\n            .await\n            .with_context(|| format!(\"attachment download failed: {url}\"))?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let body = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"attachment download failed ({status}): {body}\");\n        }\n\n        if let Some(len) = resp.content_length()\n            && len > WECHAT_MEDIA_MAX_BYTES\n        {\n            anyhow::bail!(\n                \"attachment Content-Length ({len} bytes) exceeds {} MB limit\",\n                WECHAT_MEDIA_MAX_BYTES / (1024 * 1024)\n            );\n        }\n\n        let content_type = resp\n            .headers()\n            .get(reqwest::header::CONTENT_TYPE)\n            .and_then(|value| value.to_str().ok())\n            .map(str::to_string);\n        let bytes = resp.bytes().await?.to_vec();\n\n        if bytes.len() as u64 > WECHAT_MEDIA_MAX_BYTES {\n            anyhow::bail!(\n                \"attachment exceeds {} MB limit\",\n                WECHAT_MEDIA_MAX_BYTES / (1024 * 1024)\n            );\n        }","sourceCodeStart":1174,"sourceCodeEnd":1210,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wechat.rs#L1174-L1210","documentation":"Before streaming the body, `download_remote_attachment` compares the response's `Content-Length` header against `WECHAT_MEDIA_MAX_BYTES` (100 MiB) and refuses downloads that would exceed the limit. This is an early-abort so oversized remote files are rejected without wasting bandwidth; the WeChat upload path cannot accept media beyond this size anyway.","triggerScenarios":"Sending a WeChat attachment whose `https://` target responds 2xx with `Content-Length: > 104857600`. Typical for large video/audio files, disk images, or archives hosted behind a correct server. Fires only when the server declares the size; chunked/missing headers hit the post-read check instead (error 336).","commonSituations":"Bots forwarding large video files or recordings; attachment URLs pointing at raw recordings/exports (e.g. meeting recordings, datasets) exceeding 100 MiB; server config changes making previously-small files huge (wrong file served); test fixtures using oversized blobs.","solutions":["Compress, trim, or transcode the media below 100 MiB (e.g. lower video bitrate/resolution) and send the smaller URL.","Host the oversized file elsewhere (file share, object storage with a share link) and send the link as message text rather than as an attachment.","If the Content-Length looks wrong, verify with `curl -I <url>` what the server actually declares and fix the hosting.","Do not raise `WECHAT_MEDIA_MAX_BYTES` casually — the WeChat iLink upload endpoint enforces its own ceiling and 100 MiB is the client-side contract."],"exampleFix":"# before\nattachment.target = \"https://cdn.example.com/meeting-recording.mkv\"  # 480 MB -> error 335\n\n# after: compress under the 100 MiB ceiling, or share a link\nffmpeg -i meeting-recording.mkv -b:v 800k meeting-recording-small.mp4  # < 100 MiB\nattachment.target = \"https://cdn.example.com/meeting-recording-small.mp4\"\n# or: send \"Large file: https://cdn.example.com/meeting-recording.mkv\" as plain text","handlingStrategy":"validation","validationCode":"// check declared size before sending\nasync fn declared_size(client: &reqwest::Client, url: &str) -> Option<u64> {\n    let resp = client.head(url).send().await.ok()?;\n    resp.content_length()\n}\nif declared_size(&client, url).await.unwrap_or(0) > 100 * 1024 * 1024 {\n    anyhow::bail!(\"too large for WeChat attachment; share a link instead\");\n}","typeGuard":"fn within_wechat_limit(len: u64) -> bool {\n    len <= 100 * 1024 * 1024 // WECHAT_MEDIA_MAX_BYTES\n}","tryCatchPattern":"match channel.send(msg_with_attachment(&url)).await {\n    Err(err) if err.to_string().contains(\"exceeds 100 MB limit\") => {\n        // compress / re-host under the ceiling, or send a link in text\n        let share = rehost_under_limit(&url).await?;\n        channel.send(msg_with_text(&format!(\"File: {share}\"))).await?;\n    }\n    other => other?,\n}","preventionTips":["Enforce a 100 MiB ceiling in whatever produces attachment URLs (media pipeline, export jobs).","HEAD-check Content-Length before send to fail fast without downloading.","Remember the limit applies to local files too (error 338) — keep the ceiling in one shared constant/config."],"tags":["attachment","download","size-limit","wechat","content-length"],"backgroundTag":"download-size-limit-exceeded","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}