metadata.pluginRoot in marketplace.json is ignored — bare plugin source fails validation despite being documented
FOR HUMANS
Hi, Claude Code v2.1.179 detected a docs mismatch in how plugin metadata is structured, please see below. Have a nice day!
FOR A.I.
Summary
The docs say metadata.pluginRoot lets you write a bare plugin source (e.g. "formatter") instead of a ./-prefixed relative path. In practice the validator/resolver does not apply pluginRoot: a bare source fails with plugins.N.source: Invalid input. The two relevant doc rows also contradict each other.
Environment
- Claude Code
2.1.178and2.1.179(reproduces on both), macOS.
Docs in question
On https://code.claude.com/docs/en/plugin-marketplaces:
- The optional-fields table says:
> metadata.pluginRoot — Base directory prepended to relative plugin source paths (for example, "./plugins" lets you write "source": "formatter" instead of "source": "./plugins/formatter")
- The Plugin sources table says, for a relative-path string source:
> Local directory within the marketplace repo. Must start with ./.
These conflict: the first says a bare "source": "formatter" is allowed when pluginRoot is set; the second says a relative source must start with ./. The validator enforces the second and ignores pluginRoot.
Minimal reproduction
repro/
.claude-plugin/marketplace.json
plugins/formatter/.claude-plugin/plugin.json
plugins/formatter/.claude-plugin/plugin.json:
{ "name": "formatter", "version": "0.1.0", "description": "Minimal test plugin" }
.claude-plugin/marketplace.json — the exact documented pluginRoot example:
{
"name": "pluginroot-repro",
"owner": { "name": "repro" },
"metadata": { "pluginRoot": "./plugins" },
"plugins": [
{ "name": "formatter", "source": "formatter", "description": "bare source per docs pluginRoot example" }
]
}
Run:
claude plugin validate repro
Actual:
✘ Found 1 error:
❯ plugins.0.source: Invalid input
✘ Validation failed
Changing only the source to "./plugins/formatter" (leaving pluginRoot in place) → ✔ Validation passed. So pluginRoot has no effect; the ./ prefix is what's actually required.
Expected
Either:
- The resolver/validator applies
metadata.pluginRootso a baresourceresolves to<pluginRoot>/<source>(matching the docs), or - The docs drop the
pluginRoot-enables-bare-source affordance and state plainly that every relativesourcemust start with./.
Impact beyond validation
A marketplace published with bare sources still "adds" successfully, but each affected plugin is then treated as an unreadable remote source. In the /plugin Discover panel this shows up as a missing description and Will install: · Component summary not available for remote plugin, so the plugins look broken to anyone browsing the marketplace — with no error pointing at the real cause. (Found this on an internal team marketplace; claude plugin validate was what surfaced the actual source: Invalid input.)
3 Comments
Reproduced on v2.1.206 macOS.
Adding a data point: misleading install-time error
The same root cause also surfaces at install time with a different error message that doesn't appear in this issue yet:
My marketplace.json used the documented
pluginRootform:With this,
/plugin marketplace add being-ish/agent-skillssucceeds, but installing the plugin fails with the message above.pluginRootis also ignored at install time, andvalidatedoesn't catch itI also tested whether
pluginRootworks at runtime when the source does have the./prefix: with"pluginRoot": "./plugins"+"source": "./plan-tasks",claude plugin validatepasses, but install fails with:The resolved path is
<marketplace root>/<source>with nopluginRootprepended — sopluginRootis ignored at install time as well, not just by the validator.Working fix: https://github.com/being-ish/agent-skills/pull/3/changes#diff-5352ee4067f17e7948e1425843f0a454e045bc8edf338f0bcf75c6804ddabd9aR12
FWIW, following the documented
pluginRootexample currently guarantees hitting this, so aligning the docs and the implementation (in either direction) might be a good first step.Still reproduces on v2.1.217 (macOS)
Confirming this is still present on v2.1.217 — the latest release, newer than any version cited so far in this thread (2.1.178/2.1.179/2.1.206). All three documented behaviors reproduce exactly:
1. Bare
source+pluginRoot→ validation error2. Bare
sourceinstall → misleading "unsupported version" error(This is on the latest version, so "Update Claude Code" is a dead end.)
3.
"./<name>"+pluginRoot→ validates, butpluginRootis ignored at installWith
"pluginRoot": "./plugins"+"source": "./dataweave-tester",validatepasses but install fails:The resolved path is
<marketplace root>/<source>with nopluginRootprepended — the actual files live under<marketplace root>/plugins/dataweave-tester.Working fix (same as prior reporters): remove
metadata.pluginRootentirely and use the full relative path,"source": "./plugins/dataweave-tester". That validates and installs cleanly on v2.1.217.Net: following the documented
pluginRootexample still guarantees hitting this on the current release.Confirmed — reproduced on 2.1.233 (Linux).
Using your exact minimal reproduction (marketplace with
"metadata": {"pluginRoot": "./plugins"}and a bare"source": "formatter"),claude plugin validatefails withplugins.0.source: Invalid input. Changing only the source to"./plugins/formatter"makes validation pass, sopluginRootindeed has no effect on source resolution today.You're also right that the two doc tables on https://code.claude.com/docs/en/plugin-marketplaces contradict each other: the
metadata.pluginRootrow promises bare sources, while the Plugin sources table (and the actual validator) require relative sources to start with./. The./prefix is what the CLI keys on to treat a source as a local directory, which also explains why bare sources degrade into broken-looking "remote" plugins in the Discover panel instead of erroring.This is a genuine docs/behavior mismatch and we'll fix it — either by making
pluginRootactually apply, or by removing that affordance from the docs and stating that relative sources must start with./. Until then, the workaround is exactly what you found: always write./-prefixed sources and skippluginRoot.Thanks for the unusually precise report.
🤖 Generated with Claude Code