{"record":{"id":"5bcfa3445f75b4e9","repo":"cjpais/Handy","slug":"download-verification-failed-for-model-file-is","errorCode":null,"errorMessage":"Download verification failed for model {}: file is corrupt. Please retry.","messagePattern":"Download verification failed for model (.+?): file is corrupt\\. Please retry\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/managers/model/download.rs","lineNumber":71,"sourceCode":"    /// On mismatch or read error the partial file is deleted and an error is returned,\n    /// so the next download attempt always starts from a clean state.\n    /// When `expected_sha256` is `None` (custom user models) verification is skipped.\n    fn verify_sha256(path: &Path, expected_sha256: Option<&str>, model_id: &str) -> Result<()> {\n        let Some(expected) = expected_sha256 else {\n            return Ok(());\n        };\n        match Self::compute_sha256(path) {\n            Ok(actual) if actual == expected => {\n                info!(\"SHA256 verified for model {}\", model_id);\n                Ok(())\n            }\n            Ok(actual) => {\n                warn!(\n                    \"SHA256 mismatch for model {}: expected {}, got {}\",\n                    model_id, expected, actual\n                );\n                let _ = fs::remove_file(path);\n                Err(anyhow::anyhow!(\n                    \"Download verification failed for model {}: file is corrupt. Please retry.\",\n                    model_id\n                ))\n            }\n            Err(e) => {\n                let _ = fs::remove_file(path);\n                Err(anyhow::anyhow!(\n                    \"Failed to verify download for model {}: {}. Please retry.\",\n                    model_id,\n                    e\n                ))\n            }\n        }\n    }\n\n    /// Computes the SHA256 hex digest of a file, reading in 64KB chunks to handle large models.\n    fn compute_sha256(path: &Path) -> Result<String> {\n        let mut file = File::open(path)?;","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/cjpais/Handy/blob/98a4d80cce8ad41efec2a419b59d9e81229a35d7/src-tauri/src/managers/model/download.rs#L53-L89","documentation":"After an HTTP download completes, the file's SHA256 is computed in 64KB chunks and compared against the catalog's pinned hash. A mismatch means the bytes on disk are not the published model — a corrupt transfer, a tampered or misbehaving mirror, or a catalog hash that no longer matches a re-published upstream file. The file is deleted and the caller is told to retry.","triggerScenarios":"A mirror host serving wrong or padded content that still matches the expected size; a TLS-terminating proxy corrupting bytes; upstream re-published the file while the catalog still pins the old sha256; disk corruption during the write.","commonSituations":"Custom mirror URLs that go stale when a model is updated upstream; flaky transfers where size checks pass but content differs; catalog/app version skew after a model re-release.","solutions":["Retry download_model — a fresh transfer often resolves transient corruption","If it persists, verify the catalog's sha256 against the publisher (e.g. the HF repo's LFS sha256) and update the catalog","Bypass the misbehaving mirror and use the official HuggingFace source","Check disk health and free space; disable interfering proxies"],"exampleFix":"// before\nlet outcome = downloader.download_http_resumable(...).await?;\n\n// after — corrupt file is deleted by the verifier; a fresh attempt is safe\nlet mut attempt = 0;\nlet outcome = loop {\n    attempt += 1;\n    match downloader.download_http_resumable(...).await {\n        Ok(o) => break o,\n        Err(e) if e.to_string().contains(\"file is corrupt\") && attempt < 3 => continue,\n        Err(e) => return Err(e),\n    }\n};","handlingStrategy":"retry","validationCode":"// Before reusing an already-downloaded file, verify it against the pinned hash\nfn model_file_intact(path: &Path, expected_sha256: Option<&str>) -> bool {\n    match expected_sha256 {\n        Some(expected) => ModelDownload::compute_sha256(path)\n            .map(|actual| actual == expected)\n            .unwrap_or(false),\n        None => true, // no pinned hash — nothing to validate against\n    }\n}","typeGuard":null,"tryCatchPattern":"let mut attempts = 0;\nloop {\n    attempts += 1;\n    match downloader.download_http_resumable(...).await {\n        Ok(outcome) => break Ok(outcome),\n        Err(e) if e.to_string().contains(\"file is corrupt\") && attempts < 3 => {\n            // verifier already deleted the corrupt file; retry clean\n            continue;\n        }\n        Err(e) => break Err(e),\n    }\n}","preventionTips":["Pin a sha256 for every catalog entry — it is the trust anchor for mirror downloads","Prefer official HuggingFace sources over third-party mirrors for integrity guarantees","Surface the retry affordance to the user; corrupt-transfers are usually transient"],"tags":["sha256","checksum","download-integrity","mirror"],"backgroundTag":"checksum-verification-failed","analyzedSha":"98a4d80cce8ad41efec2a419b59d9e81229a35d7","analyzedAt":"2026-08-16T20:58:09.966Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}