extraKnownMarketplaces (directory source): global registry entry silently overwritten by identically-named copy of the repo (e.g. git submodule)

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Aug 3, 2026

Summary

A project-scoped extraKnownMarketplaces declaration with a directory source is persisted into the global registry (~/.claude/plugins/known_marketplaces.json) keyed by name only. Opening a trusted copy of the same repository — including its own checkout mounted as a git submodule inside another project — silently overwrites the registered origin for every other project on the machine (last-writer-wins, no warning).

Environment

  • Claude Code 2.1.220, macOS (darwin 25.5.0)

Reproduction

  1. Repo core contains, versioned:
  • .claude-plugin/marketplace.json with "name": "my-market" and a plugin under ./plugins/...
  • .claude/settings.json with:

``json
{
"extraKnownMarketplaces": {
"my-market": { "source": { "source": "directory", "path": "." } }
},
"enabledPlugins": { "myplugin@my-market": true }
}
``

  1. Repo consumer mounts core as a pinned git submodule at consumer/core (a byte-identical copy of both files above, detached HEAD).
  2. Start any session in the real core checkout → registry entry my-market points at the real checkout. Expected.
  3. Trust the folder consumer/core and run a headless session there (cd consumer/core && claude -p "ok").
  4. Inspect ~/.claude/plugins/known_marketplaces.json: the my-market entry now points at consumer/core — the stale, pinned submodule copy. No warning was emitted; nothing indicates the slot changed owner.
  5. Start a session in the real core again → the entry is silently overwritten back. The two checkouts fight over the slot indefinitely, last boot wins.

Also observed (correct/expected behavior worth keeping): if the submodule folder is not trusted, its settings are ignored and no overwrite happens.

Impact

  • Sessions in the real repo can silently load plugin/skill content from an outdated pinned copy that happens to have booted last.
  • Any tooling that resolves the marketplace's source.path from the registry (e.g. maintenance scripts running claude plugin marketplace update / claude plugin update) operates on the wrong checkout — in our case a detached-HEAD submodule — with no signal other than downstream symptoms.
  • The relative path: "." pattern is the natural way to make a repo self-registering and portable, which makes this collision easy to hit for anyone distributing a plugin repo as a submodule.

Expected behavior (any of these would resolve it)

  1. Scope the effective registration to the declaring project, rather than materializing a single global slot keyed by name.
  2. Or refuse (or at least warn) when an extraKnownMarketplaces declaration resolves to a different source than an existing same-name entry, instead of silently overwriting.
  3. Or key registry identity by name + declaring project, instead of last-writer-wins by name alone.
  4. Or provide an explicit reconciliation command so scripts can pin the canonical origin without editing settings or depending on the global slot.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗