jdx/mise · error · eyre::Report

{}: expected exactly one manifest in index.json

Error message

{}: expected exactly one manifest in index.json

What it means

`mise oci run` streams the built OCI layout into `docker load` (src/cli/oci/run.rs → load_into_docker). It reads `index.json` and expects exactly one manifest descriptor; zero or multiple manifests (e.g. a manifest list / multi-arch index written by other tooling) abort the load (src/oci/docker_archive.rs:46).

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 6f52dcdf99)

Solutions

  1. Rebuild the layout with `mise oci build` and point `--image-dir` at that output
  2. If the layout is foreign, flatten it first: extract/split so index.json references exactly one (non-index) manifest
  3. Verify before running: `jq '.manifests | length' <image-dir>/index.json` must be 1 and its mediaType an image manifest, not an index

Example fix

# before
mise oci run --image-dir ./layouts/multi   # index.json with 3 manifests
# after
jq '.manifests | length' ./mise-oci/index.json   # -> 1
mise oci run --image-dir ./mise-oci
Defensive patterns

Strategy: validation

Validate before calling

#!/usr/bin/env bash
d="./mise-oci"
n=$(jq '.manifests | length' "$d/index.json")
mt=$(jq -r '.manifests[0].mediaType' "$d/index.json")
[ "$n" = "1" ] || { echo "expected 1 manifest, got $n" >&2; exit 1; }
[ "$mt" = "application/vnd.oci.image.manifest.v1+json" ] || { echo "not an image manifest: $mt" >&2; exit 1; }
mise oci run --image-dir "$d"

Prevention

When it happens

Trigger: Pointing `mise oci run` (or any load_into_docker path) at an `--image-dir` that was not produced by `mise oci build`: a layout exported by skopeo/umoci containing several images, an OCI image index (manifest list), or an emptied/garbled index.json.

Common situations: Reusing an image dir that other tooling wrote into; copying only part of a layout; passing the wrong directory (e.g. the parent containing several layout dirs).

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/bf888854ced8cc1e. Report an issue: GitHub.