Marketplace plugins cached without .claude-plugin/plugin.json load inline under version-string names, duplicating and merging plugin namespaces

Open 💬 0 comments Opened Jul 10, 2026 by MaxGhenis

Environment

  • Claude Code Desktop on macOS (sessions that load plugins inline; pluginUsage in ~/.claude.json records @inline entries)
  • Plugins installed from marketplaces via claude plugin install

What happens

When a marketplace defines a plugin purely through its marketplace.json entry (no .claude-plugin/plugin.json in the plugin source), the installer materializes the cache at ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/ without writing a plugin manifest.

Sessions that load plugins inline then derive the plugin name from the cache directory basename — the version string — instead of the marketplace registration name. Consequences observed:

  1. Every component appears twice. complete@policyengine-claude v3.23.0 exposes both complete:country-models:ci-fixer and 3.23.0:country-models:ci-fixer (and likewise for every agent, command, and skill in the plugin).
  2. Distinct manifest-less plugins with the same version string merge into one namespace: plugin-dev@claude-code-plugins v0.1.0 and cosilico@cosilico v0.1.0 both loaded as 0.1.0:*, mixing their components in the agent/skill lists.
  3. ~/.claude.json pluginUsage accumulates keys like "3.23.0@inline" (18k+ uses on this machine) alongside the correctly-named "complete@policyengine-claude".

A plugin whose manifest sits at the plugin root (plugin.json, old layout — cosilico@cosilico) is affected too: only .claude-plugin/plugin.json appears to be consulted. Plugins with a proper .claude-plugin/plugin.json collide on the same name as their marketplace registration and dedupe cleanly (e.g. rules-foundation).

Repro

  1. claude plugin marketplace add PolicyEngine/policyengine-claude
  2. claude plugin install complete@policyengine-claude — this plugin is defined only by a marketplace.json entry, so its cache dir contains no .claude-plugin/plugin.json
  3. Open a Claude Code Desktop session → the available agents/skills list shows both complete:* and 3.23.0:* with identical contents

Expected

Either:

  • (a) the installer synthesizes .claude-plugin/plugin.json from the marketplace entry when materializing the cache (name, version, and description are all present in the entry), or
  • (b) the inline loader resolves the plugin name from the marketplace registration rather than falling back to the directory basename.

Workaround

A SessionStart hook that synthesizes the missing manifests (re-runs each session because plugin updates regenerate cache dirs):

#!/bin/bash
CACHE="$HOME/.claude/plugins/cache"
[ -d "$CACHE" ] || exit 0
for version_dir in "$CACHE"/*/*/*/; do
  [ -f "$version_dir/.claude-plugin/plugin.json" ] && continue
  plugin=$(basename "$(dirname "$version_dir")")
  version=$(basename "$version_dir")
  mkdir -p "$version_dir/.claude-plugin"
  if [ -f "$version_dir/plugin.json" ]; then
    cp "$version_dir/plugin.json" "$version_dir/.claude-plugin/plugin.json"
  else
    printf '{"name": "%s", "version": "%s"}\n' "$plugin" "$version" \
      > "$version_dir/.claude-plugin/plugin.json"
  fi
done

🤖 Generated with Claude Code

_Claude Code version: 2.1.175 (Claude Code)_

View original on GitHub ↗