jdx/mise · error · eyre::Report

{}: index.json lists no manifests

Error message

{}: index.json lists no manifests

What it means

`mise oci push` reads the image layout directory produced by `mise oci build` and expects index.json to reference exactly one manifest. An empty manifests array means the layout has no image content — the documented contract ("build always writes exactly one manifest into index.json") is broken, so push refuses rather than upload nothing.

Source

Thrown at src/oci/registry.rs:931

    reference: &str,
    update_index: bool,
) -> Result<PushSummary> {
    eyre::ensure!(
        !crate::config::Settings::get().offline(),
        "offline mode is enabled"
    );
    let r = Reference::parse(reference)?;
    let layout = ImageLayout {
        root: image_dir.to_path_buf(),
    };

    // Resolve the layout's single manifest. `mise oci build` always writes
    // exactly one manifest into index.json.
    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!("{}: index.json lists no manifests", image_dir.display()),
        many => bail!(
            "{}: index.json lists {} manifests; multi-manifest layouts are not supported",
            image_dir.display(),
            many.len()
        ),
    };
    let manifest_bytes = layout.read_blob(&manifest_desc.digest)?;
    let manifest: ImageManifest =
        serde_json::from_slice(&manifest_bytes).wrap_err("parsing image manifest blob")?;

    // Cross-repo mount source: the base image's repository, when it lives on
    // the destination registry (and isn't the destination repo itself).
    let mount_from = manifest
        .annotations
        .get(ANNOTATION_BASE_NAME)
        .and_then(|name| Reference::parse(name).ok())
        .filter(|base| base.registry == r.registry && base.repository != r.repository)
        .map(|base| base.repository);

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Re-run `mise oci build` to regenerate a complete layout, then push
  2. Make the push step conditional on build success in CI (fail-fast between jobs)
  3. Verify the layout before pushing: index.json's manifests array should list exactly one entry with a digest whose blob exists under blobs/
Defensive patterns

Strategy: validation

Validate before calling

// Validate the layout before invoking `mise oci push`.
fn layout_has_single_manifest(image_dir: &Path) -> Result<bool, String> {
    let bytes = std::fs::read(image_dir.join("index.json")).map_err(|e| e.to_string())?;
    let index: serde_json::Value = serde_json::from_slice(&bytes).map_err(|e| e.to_string())?;
    let n = index["manifests"].as_array().map(|a| a.len()).unwrap_or(0);
    Ok(n == 1)
}

if !layout_has_single_manifest(&image_dir)? {
    anyhow::bail!("layout incomplete — re-run `mise oci build` before push");
}

Type guard

fn is_pushable_layout(image_dir: &Path) -> bool {
    std::fs::read(image_dir.join("index.json"))
        .ok()
        .and_then(|b| serde_json::from_slice::<serde_json::Value>(&b).ok())
        .and_then(|v| v["manifests"].as_array().map(|a| a.len() == 1))
        .unwrap_or(false)
}

Prevention

When it happens

Trigger: Pointing push at an image directory whose build failed or was interrupted before the manifest was written; a hand-emptied or incorrectly copied layout directory; passing the wrong --image-dir path that happens to contain an empty index.json.

Common situations: Build and push split across CI jobs where the build job failed but push ran anyway; artifacts copied incomidentally (index.json copied, blobs dir not); manual tampering with the layout directory.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/dd2da0d3fe71d7fe. Report an issue: GitHub.