metadata.pluginRoot in marketplace.json is ignored — bare plugin source fails validation despite being documented

Status Open
Reported on v2.1.178
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jun 16, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

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.178 and 2.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:

  1. The resolver/validator applies metadata.pluginRoot so a bare source resolves to <pluginRoot>/<source> (matching the docs), or
  2. The docs drop the pluginRoot-enables-bare-source affordance and state plainly that every relative source must 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.)

View original on GitHub ↗

3 Comments

januswel · 1 month ago

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:

Failed to install: This plugin uses a source type your Claude Code version does not support. Update Claude Code and try again.

My marketplace.json used the documented pluginRoot form:

{
  "metadata": { "pluginRoot": "./plugins" },
  "plugins": [
    { "name": "plan-tasks", "source": "plan-tasks" }
  ]
}

With this, /plugin marketplace add being-ish/agent-skills succeeds, but installing the plugin fails with the message above.

pluginRoot is also ignored at install time, and validate doesn't catch it

I also tested whether pluginRoot works at runtime when the source does have the ./ prefix: with "pluginRoot": "./plugins" + "source": "./plan-tasks", claude plugin validate passes, but install fails with:

Failed to install: Source path does not exist: ~/.claude/plugins/marketplaces/being-ish/plan-tasks

The resolved path is <marketplace root>/<source> with no pluginRoot prepended — so pluginRoot is 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 pluginRoot example currently guarantees hitting this, so aligning the docs and the implementation (in either direction) might be a good first step.

juliangalati · 1 month ago

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 error

{
  "metadata": { "pluginRoot": "./plugins" },
  "plugins": [{ "name": "dataweave-tester", "source": "dataweave-tester" }]
}
✘ Found 2 errors:
  ❯ plugins.0.source: Invalid input
  ❯ plugins.1.source: Invalid input
✘ Validation failed

2. Bare source install → misleading "unsupported version" error

✘ Failed to install: This plugin uses a source type your Claude Code version does not support. Update Claude Code and try again.

(This is on the latest version, so "Update Claude Code" is a dead end.)

3. "./<name>" + pluginRoot → validates, but pluginRoot is ignored at install

With "pluginRoot": "./plugins" + "source": "./dataweave-tester", validate passes but install fails:

✘ Failed to install: Source path does not exist:
  <marketplace root>/dataweave-tester

The resolved path is <marketplace root>/<source> with no pluginRoot prepended — the actual files live under <marketplace root>/plugins/dataweave-tester.

Working fix (same as prior reporters): remove metadata.pluginRoot entirely and use the full relative path, "source": "./plugins/dataweave-tester". That validates and installs cleanly on v2.1.217.

Net: following the documented pluginRoot example still guarantees hitting this on the current release.

bcherny collaborator · 11 days ago

Confirmed — reproduced on 2.1.233 (Linux).

Using your exact minimal reproduction (marketplace with "metadata": {"pluginRoot": "./plugins"} and a bare "source": "formatter"), claude plugin validate fails with plugins.0.source: Invalid input. Changing only the source to "./plugins/formatter" makes validation pass, so pluginRoot indeed 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.pluginRoot row 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 pluginRoot actually 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 skip pluginRoot.

Thanks for the unusually precise report.

🤖 Generated with Claude Code