jdx/mise · error

base image has layers in its manifest but diff_ids in its…

Error message

base image {ref_} has {} layers in its manifest but {} diff_ids in its config — refusing to emit an OCI-spec-violating image

What it means

When building on top of a base image, the builder cross-checks the number of layers in the base image's manifest against the number of diff_ids in its config blob. The OCI spec requires a 1:1 correspondence between manifest layers and config rootfs.diff_ids. If they diverge, mise refuses to emit an image that would violate the spec (and likely fail to pull/run).

Solutions

  1. Clear the cached base image (remove the cached OCI blobs/index for ref_) and pull it again from the registry.
  2. Verify the base image is well-formed, e.g. `skopeo inspect docker://<ref_>` or `crane manifest <ref_>`, and compare layer/diff_id counts.
  3. Pin the base image to a known-good digest (`ref@sha256:...`) to avoid a registry serving inconsistent data.
  4. If a mirror/proxy is in play, bypass it or fix it so manifest and config blobs come from the same source.

Example fix

// before
let base = "registry.example.com/base:latest";

// after — pin a verified digest
let base = "registry.example.com/base@sha256:abc123...";
Defensive patterns

Strategy: try-catch

Validate before calling

// before building, verify base image integrity with an external tool
// crane manifest <ref> | jq '.layers | length'
// crane config <ref> | jq '.rootfs.diff_ids | length'  # must be equal

Try / catch

match builder.send() {
    Err(e) if e.to_string().contains("diff_ids") => {
        // purge cached base image and retry the pull
        purge_base_cache();
        retry_build();
    }
    r => r?,
}

Prevention

When it happens

Trigger: build (via send) with a base image whose registry manifest lists N layers but whose config blob's rootfs.diff_ids contains M != N entries — typically from a truncated/corrupted pull, a registry serving mismatched manifest/config blobs, or a hand-edited/cached image index.

Common situations: Proxy or mirror registries serving inconsistent blobs; corrupted local OCI cache after an interrupted download; base images produced by non-compliant tooling; manifest lists resolved to an incompatible platform's config.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/e7fe5d178501c400. Report an issue: GitHub.

Appendix: source

Thrown at src/oci/builder.rs:271

                .get("rootfs")
                .and_then(|r| r.get("diff_ids"))
                .and_then(|d| d.as_array())
                .ok_or_else(|| {
                    eyre::eyre!(
                        "pulled base image {ref_} has no rootfs.diff_ids in its config \
                         — cannot produce a valid OCI image on top of it"
                    )
                })?;
            base_diff_ids = diff_ids_raw
                .iter()
                .map(|v| {
                    v.as_str().map(String::from).ok_or_else(|| {
                        eyre::eyre!("base image {ref_} has a non-string entry in rootfs.diff_ids")
                    })
                })
                .collect::<Result<Vec<_>>>()?;
            if base_diff_ids.len() != base_layers.len() {
                bail!(
                    "base image {ref_} has {} layers in its manifest but {} diff_ids in its \
                     config — refusing to emit an OCI-spec-violating image",
                    base_layers.len(),
                    base_diff_ids.len()
                );
            }
            platform = pull.platform;
            base_config_json = Some(pull.config_json);
        }

        // --- 2. Decide layer reuse and validate tool installs ---
        // A tool layer is reused from the remote cache image when tool,
        // version, in-image prefix, and file owner all match — in that case
        // the layer is never built locally and the tool doesn't need to be
        // installed at all.
        let owner_str = format!("{}:{}", owner.uid, owner.gid);
        let python_relocations: Vec<PythonRelocation> = versions
            .iter()

View on GitHub (pinned to 533346cc37)