{"record":{"id":"9cc568c5de88bce4","repo":"Hmbown/CodeWhale","slug":"staged-bundle-has-no-plugin-json-claude-plugin-plugin-json","errorCode":null,"errorMessage":"staged bundle has no plugin.json, .claude-plugin/plugin.json, kimi.plugin.json, or plugin.toml","messagePattern":"staged bundle has no plugin\\.json, \\.claude-plugin/plugin\\.json, kimi\\.plugin\\.json, or plugin\\.toml","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/plugins/install/stage.rs","lineNumber":73,"sourceCode":"        )\n    })\n}\n\n#[cfg(not(unix))]\nfn ensure_plugins_dir(user_plugins_dir: &Path) -> Result<()> {\n    fs::create_dir_all(user_plugins_dir).with_context(|| {\n        format!(\n            \"failed to create user plugins directory {}\",\n            user_plugins_dir.display()\n        )\n    })\n}\n\n/// Validate the staged tree and return the manifest name + content hash.\npub(super) fn validate_staged(staged_path: &Path) -> Result<(String, String)> {\n    let manifest_path = crate::plugins::agent_plugin::resolve_manifest_path(staged_path)\n        .ok_or_else(|| {\n            anyhow::anyhow!(\"staged bundle has no plugin.json, .claude-plugin/plugin.json, kimi.plugin.json, or plugin.toml\")\n        })?;\n    let validated = PluginManifest::validate_from_path(&manifest_path)\n        .map_err(|error| anyhow::anyhow!(\"staged plugin manifest failed validation: {error}\"))?;\n    let name = validated.manifest.plugin.name.clone();\n    validate_skill_name_segment(&name).map_err(|error| {\n        anyhow::anyhow!(\"[plugin].name is not a safe directory name: {error:#}\")\n    })?;\n    Ok((name, validated.content_hash))\n}\n\n/// Copy a local bundle directory into staging. Symlinks anywhere in the\n/// source are rejected; a stale `.installed-from` marker is never copied so\n/// provenance always reflects *this* install.\npub(super) fn stage_local_copy(\n    source: &Path,\n    user_plugins_dir: &Path,\n    max_size: u64,\n) -> Result<StagedPlugin> {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/plugins/install/stage.rs#L55-L91","documentation":"After staging a plugin bundle, the tree must contain a supported manifest. `resolve_manifest_path` scans the staged path for plugin.json, .claude-plugin/plugin.json, kimi.plugin.json, or plugin.toml; when none exists, staging fails with this error before any install proceeds.","triggerScenarios":"`validate_staged` (called from `stage_local_copy` and `stage_tarball`) is invoked on a staged directory/tarball that lacks all four supported manifest filenames.","commonSituations":"Pointing install at a directory that is not a plugin (a plain source folder or docs repo), a tarball whose manifest is nested several levels deep under a wrapper directory, or a plugin using an unsupported manifest filename.","solutions":["Add a supported manifest (plugin.json etc.) at the bundle root or under .claude-plugin/.","Repack the tarball so the manifest-bearing directory is the archive root, not nested under a wrapper folder.","Point install at the actual plugin directory instead of its parent.","If using kimi.toml/plugin.yaml or another variant, rename to a supported filename."],"exampleFix":"// before: tarball contains wrapper dir\ntar czf plugin.tgz myplugin/        # myplugin/myplugin/plugin.json\n// after\ntar czf plugin.tgz -C myplugin .    # plugin.json at archive root","handlingStrategy":"validation","validationCode":"const SUPPORTED = ['plugin.json', '.claude-plugin/plugin.json', 'kimi.plugin.json', 'plugin.toml'];\nif (!SUPPORTED.some((f) => fs.existsSync(`${stagedDir}/${f}`))) {\n  throw new Error(`staged tree ${stagedDir} has no supported plugin manifest`);\n}","typeGuard":"const hasManifest = (dir) => fs.existsSync(path.join(dir, 'plugin.json')) ||\n  fs.existsSync(path.join(dir, '.claude-plugin', 'plugin.json')) ||\n  fs.existsSync(path.join(dir, 'kimi.plugin.json')) ||\n  fs.existsSync(path.join(dir, 'plugin.toml'));","tryCatchPattern":"try {\n  await codewhale.plugins.installFromTarball(tgzPath);\n} catch (e) {\n  if (e.message.includes('staged bundle has no')) {\n    console.error('archive layout wrong: manifest must be at root or .claude-plugin/');\n  } else throw e;\n}","preventionTips":["Build archives so the manifest sits at the archive root (tar -C dir .).","Never rename the manifest to unsupported filenames.","Smoke-test install of every packaged artifact in CI."],"tags":["plugins","manifest","install","rust"],"backgroundTag":"file-not-found","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}