{"record":{"id":"52a302b503dbfbfb","repo":"cjpais/Handy","slug":"server-advertises-bytes-expected","errorCode":null,"errorMessage":"server advertises {} bytes, expected {}","messagePattern":"server advertises (.+?) bytes, expected (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/managers/model/download.rs","lineNumber":285,"sourceCode":"            let starts_at = response\n                .headers()\n                .get(reqwest::header::CONTENT_RANGE)\n                .and_then(|v| v.to_str().ok())\n                .and_then(content_range_start);\n            if starts_at != Some(resume_from) {\n                let _ = fs::remove_file(partial_path);\n                return Err(anyhow::anyhow!(\n                    \"server returned Content-Range starting at {:?}, expected {}\",\n                    starts_at,\n                    resume_from\n                ));\n            }\n        }\n        // When the catalog pins the size, a server advertising a different\n        // total is already misbehaving — reject before writing anything.\n        if let (Some(expected), Some(len)) = (expected_size, response.content_length()) {\n            if resume_from + len != expected {\n                return Err(anyhow::anyhow!(\n                    \"server advertises {} bytes, expected {}\",\n                    resume_from + len,\n                    expected\n                ));\n            }\n        }\n\n        let known_total =\n            expected_size.or_else(|| response.content_length().map(|l| resume_from + l));\n        let total_size = known_total.unwrap_or(0);\n        let mut downloaded = resume_from;\n        let mut file = if resume_from > 0 {\n            std::fs::OpenOptions::new()\n                .append(true)\n                .open(partial_path)?\n        } else {\n            std::fs::File::create(partial_path)?\n        };","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/cjpais/Handy/blob/c89b7bf38974ad317177389f5bfe28236f0d64e3/src-tauri/src/managers/model/download.rs#L267-L303","documentation":"Raised when the catalog pins an expected size and the server's advertised remaining length (Content-Length plus the resume offset) does not sum to that size, before any bytes are written. The guard exists because writing to a mis-sized expectation either corrupts the file or masks truncation. Nothing has been written yet, but unlike some siblings this path does not delete the partial — the caller can retry once state is consistent.","triggerScenarios":"Resuming a download where the server now hosts a different-sized object (rebuilt artifact) while the catalog still pins the old size; servers advertising compressed transfer-length instead of object-length.","commonSituations":"Upstream re-published/repacked the model file after the catalog snapshot; catalog size entries going stale across releases.","solutions":["Retry the download; if the catalog entry is stale it will keep failing — update the app so the refreshed catalog is used.","Delete the model's partial and cached entry, then re-download from scratch.","Try the alternate source (mirror vs. primary) in case only one host has the divergent object.","Report the model id if both hosts disagree with the pinned size — it is a catalog bug."],"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(\"server advertises\") => {\n        // size metadata disagrees; drop local state and retry fresh\n        let _ = std::fs::remove_file(partial_path);\n        manager.download_model(model_id, None).await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Pin expected sizes in the catalog and verify against Content-Length before writing (as done here).","Regenerate catalog size+hash together whenever the artifact changes.","Distrust and restart (not resume) whenever a size disagreement appears — an inconsistent server cannot be trusted for appends."],"tags":["download","content-length","size-mismatch","catalog","http"],"backgroundTag":"content-length-mismatch","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"}