.claude-plugin/PLUGIN_SCHEMA_NOTES.md
This document captures undocumented but enforced constraints of the Claude Code plugin manifest validator.
These rules are based on real installation failures, validator behavior, and comparison with known working plugins. They exist to prevent silent breakage and repeated regressions.
If you edit .claude-plugin/plugin.json, read this first.
The Claude plugin manifest validator is strict and opinionated. It enforces rules that are not fully documented in public schema references.
The most common failure mode is:
The manifest looks reasonable, but the validator rejects it with vague errors like
agents: Invalid input
This document explains why.
version (MANDATORY)The version field is required by the validator even if omitted from some examples.
If missing, installation may fail during marketplace install or CLI validation.
Example:
{
"version": "1.1.0"
}
The following fields must always be arrays:
commandsskillshooks (if present)Even if there is only one entry, strings are not accepted.
This applies consistently across all component path fields.
agents Field: DO NOT ADDWARNING: CRITICAL: Do NOT add an
"agents"field toplugin.json. The Claude Code plugin validator rejects it entirely.
The agents field is not part of the Claude Code plugin manifest schema. Any form of it -- string path, array of paths, or array of directories -- causes a validation error:
agents: Invalid input
Agent .md files under agents/ are discovered automatically by convention (similar to hooks). They do not need to be declared in the manifest.
Previously this repo listed agents explicitly in plugin.json as an array of file paths. This passed the repo's own schema but failed Claude Code's actual validator, which does not recognize the field. Removed in #1459.
commands and skills accept directory paths only when wrapped in arraysclaude plugin validate is stricter than some marketplace previewsInvalid input) and do not indicate root causeAssume the validator is hostile and literal.
hooks Field: DO NOT ADDWARNING: CRITICAL: Do NOT add a
"hooks"field toplugin.json. This is enforced by a regression test.
Claude Code v2.1+ automatically loads hooks/hooks.json from any installed plugin by convention. If you also declare it in plugin.json, you get:
Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded file.
The standard hooks/hooks.json is loaded automatically, so manifest.hooks should
only reference additional hook files.
This has caused repeated fix/revert cycles in this repo:
| Commit | Action | Trigger |
|---|---|---|
22ad036 | ADD hooks | Users reported "hooks not loading" |
a7bc5f2 | REMOVE hooks | Users reported "duplicate hooks error" (#52) |
779085e | ADD hooks | Users reported "agents not loading" (#88) |
e3a1306 | REMOVE hooks | Users reported "duplicate hooks error" (#103) |
Root cause: Claude Code CLI changed behavior between versions:
hooks declarationThe test plugin.json does NOT have explicit hooks declaration in tests/hooks/hooks.test.js prevents this from being reintroduced.
If you're adding additional hook files (not hooks/hooks.json), those CAN be declared. But the standard hooks/hooks.json must NOT be declared.
mcpServers Field: Keep the Empty Opt-OutECC keeps .mcp.json at the repository root for Codex plugin installs and manual MCP setup.
Claude Code also auto-discovers plugin-root .mcp.json files by convention, which would bundle the same MCP servers into Claude plugin installs.
The Claude plugin slug is intentionally short (ecc), but this opt-out is still required because legacy installs and strict provider gateways have failed on generated names from longer plugin identifiers.
Keep this field in .claude-plugin/plugin.json:
{
"mcpServers": {}
}
This explicit empty object prevents Claude plugin installs from auto-loading ECC's root MCP definitions.
Without the opt-out, strict OpenAI-compatible gateways can reject plugin MCP tool names such as mcp__plugin_everything-claude-code_github__create_pull_request_review because they exceed 64 characters.
Users who want the bundled MCP servers should configure them manually from .mcp.json or mcp-configs/mcp-servers.json.
These look correct but are rejected:
"agents" in any form - not a recognized manifest field, causes Invalid inputversion"hooks": "./hooks/hooks.json" - auto-loaded by convention, causes duplicate error"mcpServers": {} - re-enables root .mcp.json auto-discovery for Claude plugin installs and can produce overlong MCP tool namesAvoid cleverness. Be explicit.
{
"version": "1.1.0",
"commands": ["./commands/"],
"skills": ["./skills/"]
}
This structure has been validated against the Claude plugin validator.
Important: Notice there is NO "hooks" field and NO "agents" field. Both are loaded automatically by convention. Adding either explicitly causes errors.
Before submitting changes that touch plugin.json:
versionagents or hooks fields (both are auto-loaded by convention)"mcpServers": {} unless you are intentionally changing Claude plugin MCP bundling behaviorclaude plugin validate .claude-plugin/plugin.json
If in doubt, choose verbosity over convenience.
This repository is widely forked and used as a reference implementation.
Documenting validator quirks here:
If the validator changes, update this document first.