{"record":{"id":"fdd959397271dd29","repo":"zeroclaw-labs/zeroclaw","slug":"plugin-archive-contains-multiple-manifest-toml-fil","errorCode":null,"errorMessage":"plugin archive contains multiple manifest.toml files","messagePattern":"plugin archive contains multiple manifest\\.toml files","errorType":"console","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/plugin_registry.rs","lineNumber":241,"sourceCode":"            .split(['/', '\\\\'])\n            .any(|component| component == \"..\")\n}\n\nfn has_windows_drive_prefix(raw_name: &str) -> bool {\n    let bytes = raw_name.as_bytes();\n    bytes.len() >= 2 && bytes[1] == b':' && bytes[0].is_ascii_alphabetic()\n}\n\nfn find_manifest_dir(root: &Path) -> Result<PathBuf> {\n    let mut matches = Vec::new();\n    if root.join(\"manifest.toml\").is_file() {\n        matches.push(root.to_path_buf());\n    }\n    collect_manifest_dirs(root, &mut matches)?;\n    match matches.as_slice() {\n        [dir] => Ok(dir.clone()),\n        [] => bail!(\"plugin archive does not contain manifest.toml\"),\n        _ => bail!(\"plugin archive contains multiple manifest.toml files\"),\n    }\n}\n\nfn collect_manifest_dirs(dir: &Path, matches: &mut Vec<PathBuf>) -> Result<()> {\n    for entry in std::fs::read_dir(dir).with_context(|| format!(\"reading {}\", dir.display()))? {\n        let entry = entry?;\n        let path = entry.path();\n        if path.is_dir() {\n            if path.join(\"manifest.toml\").is_file() {\n                matches.push(path.clone());\n            }\n            collect_manifest_dirs(&path, matches)?;\n        }\n    }\n    Ok(())\n}\n\nfn copy_zip_entry_capped<R, W>(","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/src/plugin_registry.rs#L223-L259","documentation":"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.","triggerScenarios":"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.","commonSituations":"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/).","solutions":["Package exactly one plugin per archive: the zip's tree must contain a single manifest.toml","Exclude extra manifests when zipping (`zip -r p.zip . -x 'examples/*' 'vendor/*'`)","If you meant to install several plugins, publish each as its own registry entry/archive","Update the entry's sha256 after repacking"],"exampleFix":"# before: root manifest.toml + vendored/other-plugin/manifest.toml\n# after\ncd plugin-root\nzip -r ../p-0.1.0.zip . -x 'vendored/*' 'examples/*'   # exactly one manifest","handlingStrategy":"validation","validationCode":"// Count manifests before packaging/publishing (root + recursive, as the source does):\nlet matches: Vec<_> = walkdir::WalkDir::new(&pkg_root)\n    .into_iter().filter_map(Result::ok)\n    .filter(|e| e.file_name() == \"manifest.toml\")\n    .collect();\nif matches.len() != 1 { anyhow::bail!(\"expected exactly one manifest.toml\"); }","typeGuard":null,"tryCatchPattern":"// On 'multiple manifest.toml files', locate the extras via the same walk,\n// exclude them from the zip (vendored/, examples/), and republish one-plugin-per-archive.","preventionTips":["One plugin per archive, always","Exclude vendor/example trees in the zip command","Audit monorepo release scripts for accidental multi-manifest zips"],"tags":["plugin-registry","manifest","packaging","ambiguity"],"backgroundTag":"ambiguous-plugin-manifest","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}