{"record":{"id":"81e9439efd20663d","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-81e943","errorCode":null,"errorMessage":"读取安装包失败：{error}","messagePattern":"读取安装包失败：(.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/update.rs","lineNumber":242,"sourceCode":"    };\n    let response = match response.error_for_status() {\n        Ok(response) => response,\n        Err(error) => {\n            let _ = crate::diagnostic_log::append_diagnostic_log(\n                \"update.download.bad_status\",\n                json!({ \"version\": release.version, \"assetName\": release.asset_name, \"error\": error.to_string() }),\n            );\n            return Err(anyhow::anyhow!(\"下载安装包失败：{error}\"));\n        }\n    };\n    let bytes = match response.bytes().await {\n        Ok(bytes) => bytes,\n        Err(error) => {\n            let _ = crate::diagnostic_log::append_diagnostic_log(\n                \"update.download.body_failed\",\n                json!({ \"version\": release.version, \"assetName\": release.asset_name, \"error\": error.to_string() }),\n            );\n            return Err(anyhow::anyhow!(\"读取安装包失败：{error}\"));\n        }\n    };\n    let _ = crate::diagnostic_log::append_diagnostic_log(\n        \"update.download.completed\",\n        json!({\n            \"version\": release.version,\n            \"assetName\": release.asset_name,\n            \"bytes\": bytes.len()\n        }),\n    );\n    let installer_path = match download_asset_to(release, &bytes, download_dir) {\n        Ok(path) => path,\n        Err(error) => {\n            let _ = crate::diagnostic_log::append_diagnostic_log(\n                \"update.write.failed\",\n                json!({\n                    \"version\": release.version,\n                    \"assetName\": release.asset_name,","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/update.rs#L224-L260","documentation":"The final download stage reads the body with response.bytes().await; if the stream fails mid-body (connection reset, read timeout, proxy truncation) the error becomes \"读取安装包失败：{error}\" and is logged as update.download.body_failed. Headers were fine and the status was 2xx — the transfer itself broke after starting.","triggerScenarios":"Connection reset during a large installer download; the full request (including body) exceeding UPDATE_DOWNLOAD_TIMEOUT; a proxy closing the stream early so the received size never matches Content-Length.","commonSituations":"Flaky Wi-Fi or VPN on large installers; mobile tethering; aggressive corporate proxies; on-access virus scanners aborting long streams.","solutions":["Simply retry — transient truncation is the most common cause","Use a stabler network or bypass the interfering proxy/VPN for the download","If you instrument the flow, compare the downloaded size with the Content-Length header","Raise UPDATE_DOWNLOAD_TIMEOUT when building from source for consistently slow links"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in 0..3u32 {\n    match perform_update(&release, &dir).await {\n        Ok(install) => return Ok(install),\n        Err(e) if e.to_string().contains(\"读取安装包失败\") && attempt < 2 => {\n            tokio::time::sleep(std::time::Duration::from_secs(1 << attempt)).await;\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Retry truncated downloads with backoff before reporting failure to the user","Compare downloaded size to Content-Length when instrumenting custom download flows","Avoid flaky proxies/VPNs for multi-hundred-MB installer transfers"],"tags":["download","network","timeout","update"],"backgroundTag":"incomplete-download","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}