{"record":{"id":"2e3216731666eaed","repo":"zed-industries/zed","slug":"downloaded-extension-size-actual-len-does-not-ma","errorCode":null,"errorMessage":"downloaded extension size {actual_len} does not match content length {content_length}","messagePattern":"downloaded extension size (.+?) does not match content length (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/extension_host/src/extension_host.rs","lineNumber":822,"sourceCode":"                .context(\"error reading extensions\")?;\n\n            if response.status().is_client_error() {\n                let text = String::from_utf8_lossy(body.as_slice());\n                bail!(\n                    \"status error {}, response: {text:?}\",\n                    response.status().as_u16()\n                );\n            }\n\n            let mut response: GetExtensionsResponse = serde_json::from_slice(&body)?;\n\n            response\n                .data\n                .retain(|extension| !SUPPRESSED_EXTENSIONS.contains(extension.id.as_ref()));\n\n            Ok(response.data)\n        })\n    }\n\n    pub fn install_extension(\n        &mut self,\n        extension_id: Arc<str>,\n        version: Arc<str>,\n        cx: &mut Context<Self>,\n    ) {\n        self.install_or_upgrade_extension(extension_id, version, ExtensionOperation::Install, cx)\n            .detach_and_log_err(cx);\n    }\n\n    fn install_or_upgrade_extension_at_endpoint(\n        &mut self,\n        extension_id: Arc<str>,\n        url: Url,\n        operation: ExtensionOperation,\n        cx: &mut Context<Self>,\n    ) -> Task<Result<()>> {","sourceCodeStart":804,"sourceCodeEnd":840,"githubUrl":"https://github.com/zed-industries/zed/blob/9d272b036335401f339d024ea94968fd51016c40/crates/extension_host/src/extension_host.rs#L804-L840","documentation":"When downloading an extension archive, the host records the Content-Length header (if parseable), streams the body to completion, and then compares the received byte count against it. A mismatch means the body was truncated or altered in transit, and it bails before attempting gzip decompression so the corrupt tarball never reaches disk.","triggerScenarios":"Connection reset or timeout mid-download; a proxy or antivirus rewriting the body (injecting an error page); server sending a stale/incorrect Content-Length; transparent decompression by an intermediary changing the byte count.","commonSituations":"Flaky Wi-Fi/VPN links dropping large archive downloads; corporate TLS-inspection proxies; CDN inconsistencies where the header doesn't match the served object; retries after partial failures.","solutions":["Retry the download — transient truncation is the most common cause.","If it persists, verify with `curl -sI <archive-url>` that Content-Length matches what a full `curl -o` download produces.","Disable or bypass HTTP-manipulating proxies/VPN and retry.","Report to the extension publisher/host if the server itself advertises a wrong Content-Length."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"let tar_gz_bytes = retry_with_backoff(3, || async {\n    let bytes = download_extension(&client, url).await?;\n    if let Some(expected) = content_length {\n        anyhow::ensure!(bytes.len() == expected, \"truncated download\");\n    }\n    Ok(bytes)\n}).await?;","preventionTips":["Validate byte count against Content-Length immediately after streaming, before decompression.","Retry truncated downloads with exponential backoff; most mismatches are transient.","Avoid HTTP-transforming proxies for large archive downloads; verify sizes with curl when debugging."],"tags":["download","network","corruption","content-length"],"backgroundTag":"truncated-download","analyzedSha":"9d272b036335401f339d024ea94968fd51016c40","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}