{"record":{"id":"7ff693965d15c63b","repo":"zeroclaw-labs/zeroclaw","slug":"wecom-attachment-download-failed-kind-msg-id","errorCode":null,"errorMessage":"WeCom attachment download failed: kind={} msg_id={} url_target={} status={} body_preview={}","messagePattern":"WeCom attachment download failed: kind=(.+?) msg_id=(.+?) url_target=(.+?) status=(.+?) body_preview=(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wecom_ws.rs","lineNumber":1322,"sourceCode":"        let response = self\n            .client\n            .get(url)\n            .send()\n            .await\n            .with_context(|| {\n                format!(\n                    \"failed to download WeCom attachment: kind={} msg_id={} url_target={} elapsed_ms={}\",\n                    kind.as_str(),\n                    inbound.msg_id,\n                    url_target,\n                    started.elapsed().as_millis(),\n                )\n            })?;\n        let status = response.status();\n        if !status.is_success() {\n            let body = response.text().await.unwrap_or_default();\n            let body_preview = truncate_for_log(&body, 512);\n            anyhow::bail!(\n                \"WeCom attachment download failed: kind={} msg_id={} url_target={} status={} body_preview={}\",\n                kind.as_str(),\n                inbound.msg_id,\n                url_target,\n                status,\n                body_preview\n            );\n        }\n\n        if let Some(len) = response.content_length()\n            && len > self.cfg.max_file_size_bytes\n        {\n            wecom_log_warn!(\n                \"WeCom attachment skipped: declared size exceeds configured limit msg_id={} attachment_kind={} declared_bytes={} max_file_size_bytes={}\",\n                inbound.msg_id,\n                kind.as_str(),\n                len,\n                self.cfg.max_file_size_bytes","sourceCodeStart":1304,"sourceCodeEnd":1340,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wecom_ws.rs#L1304-L1340","documentation":"WeCom WS attachment downloads fetch the media URL and require a 2xx status; failures bail with a log-style message including the attachment kind, msg_id, a summarized URL target (via summarize_attachment_url_for_log), the HTTP status, and a 512-character body preview (truncate_for_log).","triggerScenarios":"An inbound WeCom message with media (image/file/voice) whose download URL returns 403 (expired URL or auth), 404 (media no longer retained), or 5xx (CDN/gateway error).","commonSituations":"Delayed processing after the signed media URL expired; wrong media proxy config; WeCom CDN issues; a proxy stripping auth headers or rewriting the URL (visible in url_target).","solutions":["Read the embedded status and body preview: 403/expired → process attachments promptly or skip; 404 → media gone, request a resend; 5xx → retry with backoff","Retry the download a bounded number of times for 5xx only","Compare url_target in the message against the expected media host to spot proxy rewrites"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"Branch on the embedded status: 5xx → bounded retry with backoff; 403/404 (expired/gone media) → skip the attachment, log msg_id, and continue processing the message; anything else → log the body preview and dead-letter.","preventionTips":["Process inbound WeCom attachments immediately rather than queueing long — signed URLs expire","Ensure proxies do not rewrite media URLs or strip auth headers (check the logged url_target)","Cap retries so permanently expired media is dropped with a log entry, not a loop"],"tags":["wecom","attachment","download","http","media","inbound"],"backgroundTag":"media-download-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}