{"record":{"id":"ccb9ce60ccf66f02","repo":"tonhowtf/omniget","slug":"size-mismatch-expected-bytes-got","errorCode":null,"errorMessage":"Size mismatch: expected {} bytes, got {}","messagePattern":"Size mismatch: expected (.+?) bytes, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/direct_downloader.rs","lineNumber":314,"sourceCode":"        };\n        download_single_stream(\n            client,\n            url,\n            &part_path,\n            existing,\n            probe.content_length,\n            progress_tx,\n            headers,\n            cancel,\n        )\n        .await?;\n    }\n\n    if let Some(expected) = probe.content_length {\n        let actual = std::fs::metadata(&part_path)?.len();\n        if expected > 0 && actual != expected {\n            let _ = std::fs::remove_file(&part_path);\n            return Err(anyhow!(\n                \"Size mismatch: expected {} bytes, got {}\",\n                expected,\n                actual\n            ));\n        }\n    }\n\n    if let Err(e) = reject_html_masquerading_as_media(&part_path) {\n        let _ = std::fs::remove_file(&part_path);\n        return Err(e);\n    }\n\n    std::fs::rename(&part_path, output)?;\n    let _ = progress_tx.send(ProgressUpdate::percent(100.0)).await;\n\n    let size = std::fs::metadata(output)?.len();\n    Ok(size)\n}","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/direct_downloader.rs#L296-L332","documentation":"download_attempt verifies the fully downloaded .part file against the Content-Length the server advertised during the probe. If the byte count on disk differs from the expected content length (and expected > 0), the partial file is deleted and this error is returned. It guards against truncated or corrupted transfers (e.g. connection dropped mid-body without a TCP error).","triggerScenarios":"The server's probe response advertised a content_length > 0, but the bytes written to part_path after the stream completed (or broke) equal a different number — truncated download, server sending fewer bytes than advertised, or a resume that started from the wrong offset.","commonSituations":"Connection dropped mid-transfer and the stream ended 'cleanly' without an error; a CDN/edge node serving a truncated body; resuming a .part file that was written by a different/older request whose range the server silently ignored; disk full causing short writes in unusual setups.","solutions":["Re-run the download — the retry wrapper deletes the .part file, so a fresh attempt re-probes Content-Length and starts clean.","Check free disk space on the output volume; a full disk can produce short writes.","Test the URL with curl and compare Content-Length vs downloaded size to confirm the server itself is misbehaving.","If resuming, delete the stale .part file manually (output + '.part') so the next attempt starts from byte 0 instead of a corrupt offset.","If a specific server chronically sends wrong Content-Length, consider disabling length verification or switching mirrors."],"exampleFix":"// before\nlet part = PathBuf::from(format!(\"{}.part\", out.display()));\nif part.exists() { /* keep it and hope resume works */ }\nlet file = downloader.download(&url, &out).await?;\n// after\nlet part = PathBuf::from(format!(\"{}.part\", out.display()));\nif part.exists() {\n    let meta = std::fs::metadata(&part)?;\n    if meta.len() == 0 || meta.modified().ok().map_or(true, |m| m.elapsed().map_or(true, |e| e.as_secs() > 3600)) {\n        let _ = std::fs::remove_file(&part); // stale/corrupt resume data -> avoid size mismatch\n    }\n}\nlet file = downloader.download(&url, &out).await?;","handlingStrategy":"validation","validationCode":"// Rust: compare the advertised Content-Length to what a quick range probe returns\nasync fn length_agrees(client: &reqwest::Client, url: &str) -> Option<bool> {\n    let r = client.head(url).send().await.ok()?;\n    let len: u64 = r.headers().get(\"content-length\")?.to_str().ok()?.parse().ok()?;\n    Some(len > 0)\n} // mismatch on resume: delete stale '<output>.part' before calling download","typeGuard":null,"tryCatchPattern":"match downloader.download(&url, &out).await {\n    Err(e) if e.to_string().starts_with(\"Size mismatch:\") => {\n        let _ = std::fs::remove_file(out.with_extension(\"part\"));\n        downloader.download(&url, &out).await // clean restart\n    }\n    other => other,\n}","preventionTips":["Delete stale .part files whose age or source URL doesn't match the current download","Ensure adequate free disk space before large downloads","Don't reuse the same output path for different URLs","If a server chronically lies about Content-Length, verify it once with curl and avoid that mirror"],"tags":["download","http","integrity","rust"],"backgroundTag":"checksum-mismatch","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}