{"record":{"id":"0f6fa361e7520167","repo":"tonhowtf/omniget","slug":"http-fatal-downloading-segment","errorCode":null,"errorMessage":"HTTP {} (fatal) downloading segment","messagePattern":"HTTP (.+?) \\(fatal\\) downloading segment","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":728,"sourceCode":"    cancel: &CancellationToken,\n) -> anyhow::Result<Vec<u8>> {\n    let mut last_err = None;\n    for attempt in 0..max_retries {\n        if cancel.is_cancelled() {\n            anyhow::bail!(\"Download cancelled\");\n        }\n\n        let result = tokio::time::timeout(SEGMENT_TIMEOUT, async {\n            let resp = apply_referer_headers(client.get(url), referer)\n                .header(\"User-Agent\", user_agent)\n                .send()\n                .await?;\n\n            let status = resp.status();\n            if !status.is_success() {\n                let code = status.as_u16();\n                if (400..500).contains(&code) && code != 429 && code != 408 {\n                    return Err(anyhow::anyhow!(\"HTTP {} (fatal) downloading segment\", code));\n                }\n                return Err(anyhow::anyhow!(\"HTTP {} downloading segment\", code));\n            }\n\n            resp.bytes()\n                .await\n                .map(|b| b.to_vec())\n                .map_err(|e| anyhow::anyhow!(e))\n        })\n        .await;\n\n        match result {\n            Ok(Ok(data)) => return Ok(data),\n            Ok(Err(e)) => {\n                if e.to_string().contains(\"(fatal)\") {\n                    return Err(e);\n                }\n                last_err = Some(e);","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L710-L746","documentation":"Raised in download_segment_with_retry when the segment HTTP response status is a 4xx client error other than 429 (rate limit) and 408 (request timeout). These are treated as fatal: retrying the same URL will not help, so the error propagates immediately without further retries. The status code is embedded in the message for diagnosis.","triggerScenarios":"Segment GET returns 404 (segment pruned from a live playlist), 403 (expired signed URL/token), or 401 (missing/invalid auth) — any 4xx except 429/408.","commonSituations":"Live stream VOD edge where old segments expire while downloading; signed CDN URLs (e.g., AWS CloudFront, Akamai) whose query-token expired mid-download; geo-blocked or auth-required manifests; HLS playlist referencing segments removed by the server.","solutions":["Re-fetch the media playlist and use fresh segment URLs (especially for live streams where segments are pruned).","For 401/403, refresh the auth token or re-sign the URL before downloading.","Check whether the playlist is behind geo/IP restrictions or requires cookies/headers you are not sending.","If segments vanish quickly on live streams, reduce the download lag behind the live edge."],"exampleFix":"// before\nif (400..500).contains(&code) && code != 429 && code != 408 {\n    return Err(anyhow::anyhow!(\"HTTP {} (fatal) downloading segment\", code));\n}\n// after\nif (400..500).contains(&code) && code != 429 && code != 408 {\n    if code == 403 || code == 401 {\n        // refresh signed URL / token once, then retry instead of failing outright\n        let fresh = refresh_segment_url(&seg_url).await?;\n        return download_once(&fresh).await;\n    }\n    return Err(anyhow::anyhow!(\"HTTP {} (fatal) downloading segment {}\", code, seg_url));\n}","handlingStrategy":"validation","validationCode":"// after resp, before treating as fatal\nlet code = resp.status().as_u16();\nif (400..500).contains(&code) && code != 429 && code != 408 {\n    if code == 401 || code == 403 {\n        // refresh auth token / signed URL once before giving up\n        refresh_credentials().await?;\n    } else if code == 404 {\n        // re-fetch playlist: live segments may have moved to a new URL\n        refresh_playlist().await?;\n    }\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"(fatal)\") => {\n        let code: u16 = /* parse from message */ 0;\n        if code == 403 || code == 401 {\n            // refresh auth and retry the segment once\n        } else {\n            return Err(e); // unrecoverable client error\n        }\n    }\n    other => other?,\n}","preventionTips":["Re-fetch the media playlist periodically on live streams so segment URLs stay fresh.","Refresh signed-URL tokens/auth cookies before they expire during long downloads.","Send required headers (Referer, Origin, User-Agent, cookies) that the CDN enforces.","Log the segment URL and status code together so 403/404 causes are immediately diagnosable."],"tags":["http","hls","network","client-error"],"backgroundTag":"http-error-response","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"}