{"record":{"id":"e39ebd9eeb139d4b","repo":"Hmbown/CodeWhale","slug":"source-is-not-a-valid-plugin-bundle-no-plugin-json-claude","errorCode":null,"errorMessage":"source is not a valid plugin bundle: no plugin.json, .claude-plugin/plugin.json, kimi.plugin.json, or plugin.toml","messagePattern":"source is not a valid plugin bundle: 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":95,"sourceCode":"    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> {\n    // Validate the source first; this also rejects symlinked roots/manifests.\n    let manifest_path =\n        crate::plugins::agent_plugin::resolve_manifest_path(source).ok_or_else(|| {\n            anyhow::anyhow!(\n                \"source is not a valid plugin bundle: no plugin.json, .claude-plugin/plugin.json, kimi.plugin.json, or plugin.toml\"\n            )\n        })?;\n    PluginManifest::validate_from_path(&manifest_path)\n        .map_err(|error| anyhow::anyhow!(\"source is not a valid plugin bundle: {error}\"))?;\n    let canonical_source = source\n        .canonicalize()\n        .with_context(|| format!(\"failed to resolve {}\", source.display()))?;\n    if let Ok(canonical_plugins) = user_plugins_dir.canonicalize()\n        && (canonical_source == canonical_plugins\n            || canonical_source.starts_with(&canonical_plugins))\n    {\n        bail!(\n            \"cannot install a bundle from inside the user plugins directory {}; \\\n             it is already in place\",\n            canonical_plugins.display()\n        );\n    }","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/plugins/install/stage.rs#L77-L113","documentation":"When installing from a local directory, the source is validated before copying. `resolve_manifest_path(source)` finds no supported manifest (plugin.json, .claude-plugin/plugin.json, kimi.plugin.json, or plugin.toml), so the directory is not a plugin bundle and staging is refused. This check also rejects symlinked roots and manifests.","triggerScenarios":"`stage_local_copy` (called by `install_inner`) is given a local path where no supported manifest file exists at the root or under .claude-plugin/.","commonSituations":"`codewhale plugin install ./path` pointed at the wrong directory (parent, repo root, or a non-plugin folder), a plugin using an unsupported manifest name, or the manifest accidentally deleted/ignored by packaging.","solutions":["cd into the directory and confirm plugin.json (or an equivalent) exists at its root.","Point install at the inner plugin directory rather than its parent or repo root.","Rename your manifest to a supported filename (plugin.json, kimi.plugin.json, or plugin.toml) or place it in .claude-plugin/.","If installing from a repo, check out the tag/branch where the manifest exists."],"exampleFix":"// before\ncodewhale plugin install ./myrepo          # no manifest at repo root\n// after\ncodewhale plugin install ./myrepo/plugins/my-plugin  # contains plugin.json","handlingStrategy":"validation","validationCode":"const SUPPORTED = ['plugin.json', '.claude-plugin/plugin.json', 'kimi.plugin.json', 'plugin.toml'];\nconst src = process.argv[2];\nif (!SUPPORTED.some((f) => fs.existsSync(path.join(src, f)))) {\n  console.error(`${src} is not a plugin bundle (no manifest)`); process.exit(1);\n}","typeGuard":"const isPluginSource = (p) => fs.existsSync(path.join(p, 'plugin.json')) ||\n  fs.existsSync(path.join(p, '.claude-plugin', 'plugin.json')) ||\n  fs.existsSync(path.join(p, 'kimi.plugin.json')) ||\n  fs.existsSync(path.join(p, 'plugin.toml'));","tryCatchPattern":"try {\n  await codewhale.plugins.install(srcDir);\n} catch (e) {\n  if (e.message.includes('source is not a valid plugin bundle')) {\n    console.error(`check that ${srcDir} contains a supported manifest`);\n  } else throw e;\n}","preventionTips":["Run install from inside the plugin directory, not the repo root.","Symlink only whole plugin directories; the check rejects symlinked roots/manifests.","Confirm the checkout/branch actually contains the manifest."],"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"}