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

  1. Retry after a delay if the status was 429/5xx (rate limit or transient registry error).
  2. Authenticate to raise registry rate limits.
  3. For 405, check the registry's OCI dist-spec compliance or use a compliant registry version.
  4. 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

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


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)