{"record":{"id":"89a350c08a975829","repo":"tonhowtf/omniget","slug":"range-not-satisfiable-restarting","errorCode":null,"errorMessage":"Range not satisfiable, restarting","messagePattern":"Range not satisfiable, restarting","errorType":"exception","errorClass":null,"httpStatus":416,"severity":"warning","filePath":"src-tauri/omniget-core/src/core/direct_downloader.rs","lineNumber":502,"sourceCode":"\n    if existing_bytes > 0 {\n        if let Some(total) = total_size {\n            if existing_bytes >= total {\n                return Ok(());\n            }\n        }\n        request = request.header(\"Range\", format!(\"bytes={}-\", existing_bytes));\n    }\n\n    let response = request.send().await?;\n\n    let mut offset = 0u64;\n    if existing_bytes > 0 {\n        if response.status() == reqwest::StatusCode::PARTIAL_CONTENT {\n            offset = existing_bytes;\n        } else if response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {\n            let _ = std::fs::remove_file(part_path);\n            return Err(anyhow!(\"Range not satisfiable, restarting\"));\n        } else if !response.status().is_success() {\n            return Err(anyhow!(\"HTTP {} downloading {}\", response.status(), url));\n        }\n    } else if !response.status().is_success() {\n        return Err(anyhow!(\"HTTP {} downloading {}\", response.status(), url));\n    }\n\n    if let Some(ct) = response.headers().get(\"content-type\") {\n        if let Ok(ct_str) = ct.to_str() {\n            if ct_str.contains(\"text/html\") {\n                return Err(anyhow!(\n                    \"Server returned HTML instead of media — URL may have expired\"\n                ));\n            }\n        }\n    }\n\n    use std::io::Write;","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/direct_downloader.rs#L484-L520","documentation":"When resuming, download_single_stream sends a Range request for the existing .part bytes. If the server answers 416 RANGE_NOT_SATISFIABLE instead of 206 PARTIAL_CONTENT, the stored offset is no longer valid (file shrank, changed, or the server can't satisfy the range), so the .part file is deleted and this error is returned to signal the caller that the transfer will restart from scratch. The retry loop treats it as a retryable attempt.","triggerScenarios":"Calling download on an output whose .part file exists and is non-empty, and the server responds HTTP 416 to the Range header — e.g. the .part is larger than the (new) file size, the remote file changed between sessions, or the server lost its ability to serve ranges.","commonSituations":"Resuming a download after the source file was replaced with a smaller version; a .part left over from a different URL written to the same output path; servers/proxies that previously supported ranges but now return 416 (config change, cache purge); clock/mirror switch changing content length mid-resume.","solutions":["Do nothing special beyond retrying — the library deletes the stale .part and the next attempt restarts from byte 0; this is usually self-healing.","Manually delete the .part file (output + '.part') before re-calling download if you want a guaranteed clean restart in one attempt.","Verify the remote file hasn't changed (size/ETag/URL) between the original attempt and the resume; if it has, download to a new output path.","Ensure the same URL/mirror is used for resume — switching mirrors with different content makes the saved offset invalid.","If a proxy strips Range support, bypass it or download without resume."],"exampleFix":"// before\n// stale .part from an older, larger version of the file\nlet file = downloader.download(&url, &out).await?; // -> 416, 'Range not satisfiable, restarting'\n// after\nlet part = out.with_extension(\"part\"); // match part_path_for() naming\nlet _ = std::fs::remove_file(&part);   // drop incompatible resume state up front\nlet file = downloader.download(&url, &out).await?; // clean full download","handlingStrategy":"retry","validationCode":"// Rust: drop resume state whose size can't fit the current remote file\nfn reset_stale_part(output: &std::path::Path, remote_len: u64) {\n    let part = output.with_extension(\"part\");\n    if let Ok(m) = std::fs::metadata(&part) {\n        if m.len() > remote_len {\n            let _ = std::fs::remove_file(&part);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"match downloader.download(&url, &out).await {\n    Err(e) if e.to_string().contains(\"Range not satisfiable\") => {\n        // library already deleted the .part; one clean retry starts from scratch\n        downloader.download(&url, &out).await\n    }\n    other => other,\n}","preventionTips":["Resume only against the same URL/mirror that produced the .part file","Clear .part files when the remote resource is known to have changed","Confirm the server honors Range requests (curl -H 'Range: bytes=0-0' -> expect 206)","Treat this error as informational — the next attempt performs a full restart"],"tags":["http","download","resume","http-416","rust"],"backgroundTag":"unexpected-http-status","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"}