zeroclaw-labs/zeroclaw · error · anyhow::Error

plugin archive contains multiple manifest.toml files

Error message

plugin archive contains multiple manifest.toml files

What it means

The mirror image of error 1415: find_manifest_dir found more than one manifest.toml in the extracted tree (root match plus recursive matches collected by collect_manifest_dirs). Because the install target is ambiguous, it bails rather than picking one.

Source

Thrown at src/plugin_registry.rs:241

            .split(['/', '\\'])
            .any(|component| component == "..")
}

fn has_windows_drive_prefix(raw_name: &str) -> bool {
    let bytes = raw_name.as_bytes();
    bytes.len() >= 2 && bytes[1] == b':' && bytes[0].is_ascii_alphabetic()
}

fn find_manifest_dir(root: &Path) -> Result<PathBuf> {
    let mut matches = Vec::new();
    if root.join("manifest.toml").is_file() {
        matches.push(root.to_path_buf());
    }
    collect_manifest_dirs(root, &mut matches)?;
    match matches.as_slice() {
        [dir] => Ok(dir.clone()),
        [] => bail!("plugin archive does not contain manifest.toml"),
        _ => bail!("plugin archive contains multiple manifest.toml files"),
    }
}

fn collect_manifest_dirs(dir: &Path, matches: &mut Vec<PathBuf>) -> Result<()> {
    for entry in std::fs::read_dir(dir).with_context(|| format!("reading {}", dir.display()))? {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() {
            if path.join("manifest.toml").is_file() {
                matches.push(path.clone());
            }
            collect_manifest_dirs(&path, matches)?;
        }
    }
    Ok(())
}

fn copy_zip_entry_capped<R, W>(

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Package exactly one plugin per archive: the zip's tree must contain a single manifest.toml
  2. Exclude extra manifests when zipping (`zip -r p.zip . -x 'examples/*' 'vendor/*'`)
  3. If you meant to install several plugins, publish each as its own registry entry/archive
  4. Update the entry's sha256 after repacking

Example fix

# before: root manifest.toml + vendored/other-plugin/manifest.toml
# after
cd plugin-root
zip -r ../p-0.1.0.zip . -x 'vendored/*' 'examples/*'   # exactly one manifest
Defensive patterns

Strategy: validation

Validate before calling

// Count manifests before packaging/publishing (root + recursive, as the source does):
let matches: Vec<_> = walkdir::WalkDir::new(&pkg_root)
    .into_iter().filter_map(Result::ok)
    .filter(|e| e.file_name() == "manifest.toml")
    .collect();
if matches.len() != 1 { anyhow::bail!("expected exactly one manifest.toml"); }

Try / catch

// On 'multiple manifest.toml files', locate the extras via the same walk,
// exclude them from the zip (vendored/, examples/), and republish one-plugin-per-archive.

Prevention

When it happens

Trigger: Installing an archive that bundles multiple plugin packages — e.g. a repo zip containing the plugin plus a vendored example/ or dependency plugin, each with their own manifest.toml.

Common situations: Zipping a monorepo/workspace with several plugins into one artifact; vendoring another plugin's source into your tree; build output that copies the manifest into two locations (root and dist/).

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/fdd959397271dd29. Report an issue: GitHub.