[BUG] Plugin runtime loader and `claude plugin validate` accept divergent schemas

Resolved 💬 3 comments Opened May 26, 2026 by harry-harish Closed May 26, 2026

What's Wrong?

claude plugin validate and the runtime plugin loader use different schemas. The CLI accepts plugin manifests that the runtime rejects with Zod errors at install/load time. This makes the CLI validator unreliable as a pre-publish check.

I hit this twice in a row on the same plugin during initial publishing — each time, the CLI returned ✔ Validation passed and gh release create succeeded, but installing the published plugin in a clean Claude Code session failed with a Zod schema error from the runtime.

Both bugs match the same closed (auto-locked) issue: #31763.

What Should Happen?

claude plugin validate should use the same schema the runtime uses (or stricter). A manifest accepted by validate should always install successfully.

Error Messages/Logs

Case 1 — userConfig.<field>.enum (runtime, Claude Code 2.1.130+):

Failed to install: Plugin temp_local_1779351806694_0gsi8e has an invalid manifest file at
/Users/<user>/.claude/plugins/cache/.../.claude-plugin/plugin.json.

Validation errors: userConfig.default_framework: Unrecognized key: "enum",
userConfig.default_ui_framework: Unrecognized key: "enum",
userConfig.package_manager: Unrecognized key: "enum"

Case 2 — hooks/hooks.json without outer hooks wrapper (runtime, Claude Code 2.1.150):

Hook load failed: [
  {
    "expected": "record",
    "code": "invalid_type",
    "path": ["hooks"],
    "message": "Invalid input: expected record, received undefined"
  }
]

In both cases, claude plugin validate . returned ✔ Validation passed immediately before publishing.

Steps to Reproduce

Case 1 — userConfig.enum

  1. In a plugin directory, create .claude-plugin/plugin.json with a userConfig field that uses an enum array (a pattern that older plugin docs / JSON-Schema convention would suggest):

``json
{
"name": "demo-plugin",
"version": "0.1.0",
"description": "Demo",
"userConfig": {
"framework": {
"type": "string",
"title": "Framework",
"description": "Pick one",
"enum": ["a", "b", "c"],
"default": "a"
}
}
}
``

  1. Add a minimal .claude-plugin/marketplace.json and run claude plugin validate .✔ Validation passed.
  1. Publish, then try /plugin install demo-plugin@your-marketplace in a clean Claude Code session.
  1. Runtime error: Validation errors: userConfig.framework: Unrecognized key: "enum".

Case 2 — missing hooks wrapper in hooks/hooks.json

  1. In a plugin directory, create hooks/hooks.json with event arrays at the top level (matching the format that ~/.claude/settings.json accepts — which is what some older plugin docs suggested):

``json
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{ "type": "command", "command": "echo hi", "timeout": 5 }
]
}
]
}
``

  1. Run claude plugin validate .✔ Validation passed.
  1. Install the plugin, then run /doctor.
  1. Doctor reports: Hook load failed: expected record at path "hooks", received undefined.

The runtime requires { "hooks": { "PostToolUse": [...] } }, but claude plugin validate accepts the unwrapped form.

Suggested Fix

Make claude plugin validate reuse the same Zod schemas the runtime loader uses (or have both consume the same source-of-truth). The Zod errors the runtime produces are already excellent — they'd be just as useful at CLI time, before publishing.

Until then, plugin authors have to do a fresh install on every release to confirm the runtime accepts what the validator did, and validate is effectively just a syntax check.

Related (closed, auto-locked, same error message): #31763.

Claude Model

Not relevant — this is a CLI / plugin-loader bug, not a model behavior issue.

Regression

Don't know — I'm a first-time plugin author. Both schemas may have been like this from the start.

Version

Hit on Claude Code 2.1.130 (Case 1, initial install attempts) and 2.1.150 (Case 2, doctor output). Both reproductions still happen on 2.1.150.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗