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

plugin archive does not contain manifest.toml

Error message

plugin archive does not contain manifest.toml

What it means

After extraction, find_manifest_dir walks the extracted tree looking for manifest.toml (checking the root first, then recursively via collect_manifest_dirs). Zero matches means the archive is not a valid ZeroClaw plugin package, so install bails instead of guessing an entry point.

Source

Thrown at src/plugin_registry.rs:240

        || raw_name
            .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(())
}

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Confirm what is actually inside: `unzip -l plugin.zip` — if there is no manifest.toml, the artifact is wrong
  2. Rebuild the plugin zip from the directory that directly contains manifest.toml
  3. If you publish the registry, fix the entry URL to point at the correctly packaged artifact and update its sha256
  4. For source-only plugins, package them first (add manifest.toml with name/version) before registry distribution

Example fix

# before: zip of repo root (README, src/, no manifest)
# after
zip -r p-0.1.0.zip dist/          # dist/ contains manifest.toml + code
# republish index entry { url, sha256 } for p-0.1.0.zip
Defensive patterns

Strategy: validation

Validate before calling

// Validate packaging before publishing (mirror find_manifest_dir's contract):
let n = walkdir::WalkDir::new(&extracted_root)
    .into_iter()
    .filter_map(Result::ok)
    .filter(|e| e.file_name() == "manifest.toml")
    .count();
assert_eq!(n, 1, "archive must contain exactly one manifest.toml");

Type guard

fn is_valid_plugin_package(root: &Path) -> bool {
    root.join("manifest.toml").is_file()
        && /* recursive scan finds no second manifest */ true
}

Try / catch

// On 'does not contain manifest.toml', open the zip (`unzip -l`) to see what
// was actually packed, fix the packaging step, and republish with a new sha256.

Prevention

When it happens

Trigger: Installing a plugin archive that contains no manifest.toml anywhere — e.g. a source repo zip without packaging, a wrongly-built artifact, or the entry URL pointing at some other zip entirely.

Common situations: Packing the repo root instead of the packaged plugin dir; forgetting the manifest in the build script; registry entry URL accidentally referencing a random zip; nested build output (target/) zipped instead of dist/.

Related errors


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