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
- Rebuild the layout with `mise oci build` and point `--image-dir` at that output
- If the layout is foreign, flatten it first: extract/split so index.json references exactly one (non-index) manifest
- 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
- Only feed `mise oci run` image dirs produced by `mise oci build`
- When exporting layouts with skopeo, copy a single platform manifest, not an index
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
- {bin} get failed for {server}: {}
- `docker load` failed ({}): {}. Ensure the docker daemon is r
- `auth` for {key} is not `user:password`
- {bin} get failed for {server}: {stderr}
- {image_dir.display}: expected exactly one manifest in index.
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/bf888854ced8cc1e.
Report an issue: GitHub.