jdx/mise · error

: expected exactly one manifest in index.json

Error message

{}: expected exactly one manifest in index.json

What it means

When loading an OCI image layout into docker, the library parses the layout's `index.json` and expects it to contain exactly one manifest descriptor. An index with zero or multiple manifests is ambiguous — the loader would not know which image to stream to `docker load` — so it fails with the image directory path in the message.

Solutions

  1. Regenerate the image layout so index.json contains a single manifest (rebuild the image with `mise oci build`).
  2. If the layout is multi-manifest, pick/extract the single desired manifest into its own layout directory before loading.
  3. Validate index.json contents (manifests array length == 1) before calling the loader.
Defensive patterns

Strategy: validation

Validate before calling

let index: serde_json::Value =
    serde_json::from_slice(&std::fs::read(layout.join("index.json"))?)?;
let n = index["manifests"].as_array().map(|a| a.len()).unwrap_or(0);
assert_eq!(n, 1, "index.json must contain exactly one manifest, got {n}");

Try / catch

match load_into_docker(dir) {
    Err(e) if e.to_string().contains("expected exactly one manifest") => {
        // rebuild layout or pick a single manifest before retrying
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling `load_into_docker` on an image directory whose `index.json` has an empty `manifests` array or more than one entry (e.g. a multi-arch index or an index accumulated from several builds).

Common situations: Pointing the loader at a registry-style layout exported with `docker save` of a multi-platform image; reusing an OCI layout directory that was appended to by multiple builds; a corrupted or hand-edited index.json.

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/bf888854ced8cc1e. Report an issue: GitHub.

Appendix: source

Thrown at src/oci/docker_archive.rs:46

    #[serde(rename = "Config")]
    config: String,
    #[serde(rename = "RepoTags")]
    repo_tags: Vec<String>,
    #[serde(rename = "Layers")]
    layers: Vec<String>,
}

/// Stream the OCI layout at `image_dir` into `docker load`, tagging the
/// loaded image as `tag`.
pub(crate) fn load_into_docker(image_dir: &Path, tag: &str) -> Result<()> {
    let layout = ImageLayout {
        root: image_dir.to_path_buf(),
    };
    let index_bytes = crate::file::read(image_dir.join("index.json"))?;
    let index: ImageIndex = serde_json::from_slice(&index_bytes).wrap_err("parsing index.json")?;
    let manifest_desc = match index.manifests.as_slice() {
        [one] => one,
        _ => bail!(
            "{}: expected exactly one manifest in index.json",
            image_dir.display()
        ),
    };
    let manifest_bytes = layout.read_blob(&manifest_desc.digest)?;
    let manifest: ImageManifest =
        serde_json::from_slice(&manifest_bytes).wrap_err("parsing image manifest blob")?;
    let config_bytes = layout.read_blob(&manifest.config.digest)?;

    // Validate every layer digest before it's used as a path component in
    // `write_docker_archive` (which reads via `blob_path`, bypassing the
    // check `read_blob` performs) — a crafted `--image-dir` layout could
    // otherwise escape the blobs directory with `sha256:../…`.
    for layer in &manifest.layers {
        crate::oci::layout::validate_sha256_digest(&layer.digest)?;
    }

    let mut command = Command::new("docker");

View on GitHub (pinned to 533346cc37)