jdx/mise · error
fetching failed
Error message
fetching {manifest_url} failed: {} What it means
Thrown by `fetch_remote_image` when probing a previously pushed manifest in the registry returns a non-success status other than 404 (treated as cache miss) or 401/403 (treated as unreadable private repo / cache miss). Any other failing status aborts the push/pull flow.
Solutions
- Retry after a delay if the status was 429/5xx (rate limit or transient registry error).
- Authenticate to raise registry rate limits.
- For 405, check the registry's OCI dist-spec compliance or use a compliant registry version.
- Inspect proxy/load-balancer configuration for self-hosted registries.
Defensive patterns
Strategy: retry
Try / catch
match fetch_remote_image(tag) {
Err(e) if e.to_string().contains("429") || e.to_string().contains("50") => backoff_retry(),
Err(e) => return Err(e),
ok => ok,
} Prevention
- Authenticate to avoid Docker Hub 429 rate limits.
- Avoid treating probe failures as fatal — the code already treats 404/401/403 as cache miss; only unexpected statuses bubble up.
- For self-hosted registries, verify HEAD-on-manifest support (dist-spec compliance) before enabling cache checks.
When it happens
Trigger: HEAD/GET of the remote manifest URL during `fetch_remote_image` returning e.g. 429 rate limit, 500 registry error, or 405 method not allowed from a non-compliant registry.
Common situations: Docker Hub rate limiting (429) during CI; a registry that doesn't implement HEAD on manifests; transient registry backend errors (5xx); misconfigured reverse proxy in front of a self-hosted registry.
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
- fetching failed
- blob chunk upload failed
- blob upload failed
- fetching blob failed
- fetching current index for
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/398edb3a52fe43d8.
Report an issue: GitHub.
Appendix: source
Thrown at src/oci/registry.rs:812
.get(&manifest_url)
.header("Accept", index_accept.join(", "));
if let Some(a) = auth {
rb = rb.header("Authorization", a);
}
Ok(rb)
})
.await
.wrap_err_with(|| format!("fetching {manifest_url}"))?;
match resp.status() {
StatusCode::OK => {}
// No previous image under this ref (404), or the ref exists but the
// registry won't serve it as a single manifest with our Accept
// headers — both are just "no cache".
StatusCode::NOT_FOUND => return Ok(None),
// An auth failure here (private repo we can't read) is also a cache
// miss rather than a push-stopping error.
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => return Ok(None),
s => bail!("fetching {manifest_url} failed: {}", s.as_u16()),
}
let mut body: serde_json::Value = resp.json().await?;
// An index (multi-arch, e.g. a tag maintained with --update-index):
// descend into the entry for the build platform so its layers remain
// reusable.
if body.get("manifests").map(|m| m.is_array()).unwrap_or(false) {
// Match the same canonical identity `upsert_platform_manifest` uses,
// so descent and upsert agree on which entry represents this host's
// platform (arch/os normalized, arm64 variant filled). The host has
// no variant / os.version.
let host =
platform_identity_parts(std::env::consts::ARCH, std::env::consts::OS, None, None);
let digest = body
.get("manifests")
.and_then(|m| m.as_array())
.and_then(|entries| {
entries.iter().find(|e| {
let p = e.get("platform");View on GitHub (pinned to 533346cc37)