Hmbown/CodeWhale · error
staged plugin manifest failed validation
Error message
staged plugin manifest failed validation: {error} What it means
The staged bundle's manifest was found, but `PluginManifest::validate_from_path` rejected it (bad JSON/TOML, missing required fields like [plugin].name or version, wrong types). The original validator error is embedded in the message so the specific failing field is visible.
Solutions
- Read the embedded `{error}` detail and fix that exact manifest field.
- Validate plugin.json parses as JSON/TOML with a linter (e.g. `jq . plugin.json`).
- Add required fields ([plugin].name, version, description) matching the current schema.
- Copy the manifest shape from a known-working plugin and adjust values.
Example fix
// before: plugin.json
{ "name": "my-plugin" }
// after
{ "plugin": { "name": "my-plugin", "version": "1.0.0", "description": "..." } } Defensive patterns
Strategy: validation
Validate before calling
const manifest = JSON.parse(fs.readFileSync('plugin.json', 'utf8'));
const required = ['plugin.name', 'plugin.version'];
for (const key of required) {
if (!key.split('.').reduce((o, k) => o?.[k], manifest)) {
throw new Error(`plugin.json missing required field: ${key}`);
}
} Type guard
function isValidManifest(m) {
return m && typeof m === 'object' && m.plugin &&
typeof m.plugin.name === 'string' && m.plugin.name.length > 0 &&
typeof m.plugin.version === 'string';
} Try / catch
try {
await codewhale.plugins.install(src);
} catch (e) {
if (e.message.startsWith('staged plugin manifest failed validation')) {
console.error('fix plugin.json per the embedded detail:', e.message);
} else throw e;
} Prevention
- Lint plugin.json with `jq .` or a JSON schema validator before install.
- Keep manifests in version control and validate them in CI.
- Follow the current schema; copy the shape from a working plugin.
When it happens
Trigger: `validate_staged` (called from `stage_local_copy` and `stage_tarball`) calls `PluginManifest::validate_from_path(&manifest_path)` and the validator returns an error during plugin install staging.
Common situations: Hand-written manifest missing required keys, JSON/TOML syntax errors, wrong field types (e.g. version as a number), manifests produced by a newer/older schema version.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- source is not a valid plugin bundle
- installed plugin has no supported manifest
- source is not a valid plugin bundle: no plugin.json…
- staged bundle has no plugin.json…
- plugin name is not a safe directory name
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/0eb0dd4fd97d1345.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/plugins/install/stage.rs:76
#[cfg(not(unix))]
fn ensure_plugins_dir(user_plugins_dir: &Path) -> Result<()> {
fs::create_dir_all(user_plugins_dir).with_context(|| {
format!(
"failed to create user plugins directory {}",
user_plugins_dir.display()
)
})
}
/// Validate the staged tree and return the manifest name + content hash.
pub(super) fn validate_staged(staged_path: &Path) -> Result<(String, String)> {
let manifest_path = crate::plugins::agent_plugin::resolve_manifest_path(staged_path)
.ok_or_else(|| {
anyhow::anyhow!("staged bundle has no plugin.json, .claude-plugin/plugin.json, kimi.plugin.json, or plugin.toml")
})?;
let validated = PluginManifest::validate_from_path(&manifest_path)
.map_err(|error| anyhow::anyhow!("staged plugin manifest failed validation: {error}"))?;
let name = validated.manifest.plugin.name.clone();
validate_skill_name_segment(&name).map_err(|error| {
anyhow::anyhow!("[plugin].name is not a safe directory name: {error:#}")
})?;
Ok((name, validated.content_hash))
}
/// Copy a local bundle directory into staging. Symlinks anywhere in the
/// source are rejected; a stale `.installed-from` marker is never copied so
/// provenance always reflects *this* install.
pub(super) fn stage_local_copy(
source: &Path,
user_plugins_dir: &Path,
max_size: u64,
) -> Result<StagedPlugin> {
// Validate the source first; this also rejects symlinked roots/manifests.
let manifest_path =
crate::plugins::agent_plugin::resolve_manifest_path(source).ok_or_else(|| {View on GitHub (pinned to 73e0f67d83)