jdx/mise · error
fetching blob failed
Error message
fetching blob {url} failed: {} What it means
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.
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.
Defensive patterns
Strategy: retry
Try / catch
if let Err(e) = download_blob(url) {
if is_retryable(&e.to_string()) { backoff_retry(3); }
else if e.to_string().contains("404") { re_pull_manifest_and_retry(); }
else { return Err(e); }
} Prevention
- 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.
When it happens
Trigger: `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).
Common situations: 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.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/3bb4b8d203fc61fc.
Report an issue: GitHub.
Appendix: source
Thrown at src/oci/registry.rs:551
url: &str,
pr: Option<&dyn SingleReport>,
) -> Result<Vec<u8>> {
let resp = session
.send(|auth| {
let mut rb = HTTP.reqwest()?.get(url);
if let Some(a) = auth {
rb = rb.header("Authorization", a);
}
Ok(rb)
})
.await
.wrap_err_with(|| format!("GET {url}"))?;
let status = resp.status();
if !status.is_success() {
// 5xx/408/429 become transient reqwest status errors (retried by the
// caller); other statuses fall through to a deterministic failure.
resp.error_for_status_ref()?;
bail!("fetching blob {url} failed: {}", status.as_u16());
}
if let Some(pr) = pr {
if let Some(len) = resp.content_length() {
pr.set_length(len);
}
pr.set_position(0);
}
let mut resp = resp;
let mut bytes = Vec::new();
while let Some(chunk) = resp.chunk().await? {
bytes.extend_from_slice(&chunk);
if let Some(pr) = pr {
pr.inc(chunk.len() as u64);
}
}
Ok(bytes)
}
View on GitHub (pinned to 533346cc37)