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

  1. Read the embedded `{error}` detail and fix that exact manifest field.
  2. Validate plugin.json parses as JSON/TOML with a linter (e.g. `jq . plugin.json`).
  3. Add required fields ([plugin].name, version, description) matching the current schema.
  4. 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

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


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)