{"record":{"id":"3bb4b8d203fc61fc","repo":"jdx/mise","slug":"fetching-blob-url-failed","errorCode":null,"errorMessage":"fetching blob {url} failed: {}","messagePattern":"fetching blob (.+?) failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/oci/registry.rs","lineNumber":551,"sourceCode":"    url: &str,\n    pr: Option<&dyn SingleReport>,\n) -> Result<Vec<u8>> {\n    let resp = session\n        .send(|auth| {\n            let mut rb = HTTP.reqwest()?.get(url);\n            if let Some(a) = auth {\n                rb = rb.header(\"Authorization\", a);\n            }\n            Ok(rb)\n        })\n        .await\n        .wrap_err_with(|| format!(\"GET {url}\"))?;\n    let status = resp.status();\n    if !status.is_success() {\n        // 5xx/408/429 become transient reqwest status errors (retried by the\n        // caller); other statuses fall through to a deterministic failure.\n        resp.error_for_status_ref()?;\n        bail!(\"fetching blob {url} failed: {}\", status.as_u16());\n    }\n    if let Some(pr) = pr {\n        if let Some(len) = resp.content_length() {\n            pr.set_length(len);\n        }\n        pr.set_position(0);\n    }\n    let mut resp = resp;\n    let mut bytes = Vec::new();\n    while let Some(chunk) = resp.chunk().await? {\n        bytes.extend_from_slice(&chunk);\n        if let Some(pr) = pr {\n            pr.inc(chunk.len() as u64);\n        }\n    }\n    Ok(bytes)\n}\n","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/oci/registry.rs#L533-L569","documentation":"Thrown when downloading an OCI layer/config blob fails with a non-success HTTP status after retries. Transient statuses (5xx/408/429) are first converted to retryable reqwest errors; any other status reaches this deterministic `bail!` with the status code.","triggerScenarios":"`download_blob_once` receiving a non-success status on the blob GET (e.g. 404 for a blob missing from the registry, 403 for no read permission).","commonSituations":"A base image manifest references a blob that has been garbage-collected by the registry; blob deleted after manifest fetch (manifest/blob skew); read-protected registry storage; interrupted/failed CDN delivery returning 403.","solutions":["Re-pull the image so a fresh manifest referencing existing blobs is fetched.","Check that the blob still exists in the registry (`docker manifest inspect` + registry API GET).","Verify credentials grant read access to the repository's blob storage.","Retry if the status was transient (5xx/408/429) — those are retried automatically, so a persistent failure means a non-transient cause."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(e) = download_blob(url) {\n    if is_retryable(&e.to_string()) { backoff_retry(3); }\n    else if e.to_string().contains(\"404\") { re_pull_manifest_and_retry(); }\n    else { return Err(e); }\n}","preventionTips":["Pull the manifest and blobs close together in time to avoid blob GC skew.","Use digest-pinned references so the manifest can't change between fetches.","Ensure credentials include blob read access, not just catalog access."],"tags":["network","http","oci","blob-download"],"backgroundTag":"http-error-response","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}