{"record":{"id":"30d5fba592996a4e","repo":"cjpais/Handy","slug":"server-returned-http","errorCode":null,"errorMessage":"server returned HTTP {}","messagePattern":"server returned HTTP (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/managers/model/download.rs","lineNumber":259,"sourceCode":"        // 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        }\n        // On a 206, trust but verify the offset: a reply starting anywhere but\n        // exactly our partial's end would silently corrupt the file on append.\n        if resume_from > 0 && response.status() == reqwest::StatusCode::PARTIAL_CONTENT {\n            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","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/cjpais/Handy/blob/98a4d80cce8ad41efec2a419b59d9e81229a35d7/src-tauri/src/managers/model/download.rs#L241-L277","documentation":"Generic non-success HTTP status on the download GET, after the specialized 416 and 200-to-Range handling. The actual status code is embedded in the message. The request reached the server, but the server refused it or failed — the reqwest error layer for connect/DNS failures is separate from this check.","triggerScenarios":"404/410 for dead or removed model URLs; 401/403 for gated HuggingFace repos or expired pre-signed URLs; 429 rate limiting; 5xx server outages; 451 geo-blocks.","commonSituations":"HF repo made private or deleted after the catalog shipped; mirror link rot; rate limits while re-downloading large models; CDN incidents.","solutions":["Read the embedded status: 429/5xx — wait and retry with backoff; 401/403 — authenticate or switch to an open source; 404 — the URL is wrong, update the catalog or the app","Confirm the URL responds: curl -I <url>","Switch the model source to the official HuggingFace endpoint","Update Handy — catalog URL fixes ship with app releases"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"async fn download_url_ok(url: &str) -> bool {\n    match reqwest::Client::new().head(url).send().await {\n        Ok(resp) => resp.status().is_success(),\n        Err(_) => false,\n    }\n}","typeGuard":null,"tryCatchPattern":"match downloader.download_http_resumable(...).await {\n    Ok(outcome) => Ok(outcome),\n    Err(e) if e.to_string().starts_with(\"server returned HTTP\") => {\n        let status = e.to_string();\n        if status.contains(\"429\") || status.contains(\"50\") {\n            tokio::time::sleep(backoff).await;   // transient: back off and retry\n            downloader.download_http_resumable(...).await\n        } else {\n            Err(e)                               // 403/404: fix the URL/source instead\n        }\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Add a preflight status check before launching multi-GB downloads","Distinguish transient (429/5xx) from permanent (401/403/404) statuses in handlers","Keep catalog URLs pointing at stable, official endpoints"],"tags":["http-status","download","server-error"],"backgroundTag":"http-error-status","analyzedSha":"98a4d80cce8ad41efec2a419b59d9e81229a35d7","analyzedAt":"2026-08-16T20:58:09.966Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}