{"record":{"id":"858070e3eb8436c7","repo":"cjpais/Handy","slug":"server-object-ends-before-the-expected-size-http","errorCode":null,"errorMessage":"server object ends before the expected size (HTTP 416)","messagePattern":"server object ends before the expected size \\(HTTP 416\\)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/managers/model/download.rs","lineNumber":245,"sourceCode":"            r = tokio::time::timeout(DOWNLOAD_STALL_TIMEOUT, request.send()) => r\n                .map_err(|_| anyhow::anyhow!(\n                    \"no response within {}s from {}\",\n                    DOWNLOAD_STALL_TIMEOUT.as_secs(), url\n                ))??,\n            _ = cancel_token.cancelled() => return Ok(HttpDownloadOutcome::Cancelled),\n        };\n\n        // 416 to our Range request means its start is at or past the object's\n        // end. With a catalog size in hand that can only mean the server's\n        // object is *smaller* than expected (a full-size partial never issues\n        // a request — handled above), and with no hash there is no trusted\n        // signal to bless the partial: both restart clean. Only a hash can\n        // genuinely finish a partial here. Without a Range in flight a 416 is\n        // just a broken server, which the generic status check below rejects.\n        if resume_from > 0 && response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {\n            if expected_size.is_some() || expected_sha256.is_none() {\n                let _ = fs::remove_file(partial_path);\n                return Err(anyhow::anyhow!(\n                    \"server object ends before the expected size (HTTP 416)\"\n                ));\n            }\n            Self::verify_file_with_events(model_id, partial_path, expected_sha256, emit).await?;\n            return Ok(HttpDownloadOutcome::Completed);\n        }\n        // A 200 to a Range request means the server ignored it and is sending\n        // the whole file; appending it to the partial would corrupt the model.\n        if resume_from > 0 && response.status() == reqwest::StatusCode::OK {\n            let _ = fs::remove_file(partial_path);\n            resume_from = 0;\n        }\n        if !response.status().is_success() {\n            return Err(anyhow::anyhow!(\n                \"server returned HTTP {}\",\n                response.status()\n            ));\n        }","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/cjpais/Handy/blob/c89b7bf38974ad317177389f5bfe28236f0d64e3/src-tauri/src/managers/model/download.rs#L227-L263","documentation":"Raised when a Range resume request gets HTTP 416 (Range Not Satisfiable) and the server's object provably ends before the expected size — with a catalog-pinned expected_size, or with no hash that could vouch for the partial. The server is saying 'my file ends at or before where your partial begins', so the partial can never be completed; it is deleted and a clean restart is the only safe path.","triggerScenarios":"Resuming from a large partial against a server whose object shrank or was replaced by a smaller one; a catalog entry whose expected_size is larger than the file the mirror actually hosts; stale partials after the upstream re-published a smaller model.","commonSituations":"Mirror divergence: primary host re-uploaded a smaller/repacked artifact while the catalog kept the old size; interrupted downloads resumed days later after the file changed upstream.","solutions":["Retry — the partial is already deleted, so the next attempt downloads fresh from byte 0.","If it repeats immediately, the catalog metadata (expected size) disagrees with the server's actual object — switch to the primary URL or report the catalog entry.","Clear the model's partial files manually only if the app cannot (unusual; deletion already happened here).","Confirm no local clock/state weirdness by deleting the model entry entirely and re-adding it from the current catalog."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match manager.download_model(model_id, None).await {\n    Ok(_) => Ok(()),\n    Err(e) if e.to_string().contains(\"HTTP 416\") => {\n        // partial deleted automatically; a fresh attempt re-downloads from zero\n        manager.download_model(model_id, None).await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Keep catalog metadata (size/hash) in sync with the actual hosted artifact — publish catalog updates with file changes.","Treat 416-on-resume as 'upstream changed' and prefer a full restart over clever partial salvage.","When hosting models yourself, never replace an object in place at a stable URL; publish under a new path."],"tags":["download","http-416","range","resume","model"],"backgroundTag":"http-416-range-not-satisfiable","analyzedSha":"c89b7bf38974ad317177389f5bfe28236f0d64e3","analyzedAt":"2026-08-17T10:29:55.597Z","contentChangedAt":"2026-08-17T10:29:55.597Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}