Plugin skill discovery: multi-scope installs silently load intersection, project-scope leaks across unrelated cwds
Summary
When the same plugin is installed at both project and user scope at different versions, the active skill set in any session equals the intersection of skills present in all scopes, not the union or the higher-precedence scope. Project-scope entries also appear to apply to sessions launched in unrelated working directories, not only inside their pinned projectPath.
Net effect: a freshly added skill in a newer plugin version never appears in /skills, even after /reload-plugins, full restart, version bumps, and re-installs — until the older project-scope entry is removed.
Environment
- Claude Code: 2.1.119
- macOS Darwin 25.4.0
- Plugin used to repro:
reclaim-core@reclaim-marketplace(https://github.com/Reclaim-Security/reclaim-claude-plugin)
Repro
- Install plugin
X@1.0at project scope inside~/projA. Plugin has skillsa,b. - Add a new skill
cto plugin upstream and bump to1.1. - From any cwd, run
/plugin install X@marketplaceto install at user scope.~/.claude/plugins/installed_plugins.jsonnow lists two entries:
scope: project, projectPath: ~/projA, version: 1.0scope: user, version: 1.1
cd ~/projB(entirely unrelated to~/projA)./skills— onlyaandbshown.cmissing./reload-pluginsreports0 skillsreloaded; restart Claude Code fully —cstill missing.- Bump plugin to
1.2,1.3— same. Three round-trip update cycles do not surfacec. - Manually remove the project-scope entry from
installed_plugins.jsonand restart —cappears.
Expected
- Project-scope plugin should only affect sessions whose cwd is inside
projectPath. - When multiple scopes are present for the same plugin, the higher-precedence scope (typically the more specific one for the cwd) should win — not the intersection.
/reload-pluginsshould rescan skill files on disk and report a non-zero count when new SKILL.md files are present in the active version.
Actual
- Project-scope shadows user-scope across all sessions regardless of cwd.
- Loaded skill set =
skills(scope_A) ∩ skills(scope_B), silently filtered, no warning. /reload-pluginsalways reports0 skillsfor this plugin even after legitimately new skill files are added; only full restart picks them up, and only after the shadowing scope is removed.
Diagnostic snippet
jq '.plugins["<plugin>@<marketplace>"]' ~/.claude/plugins/installed_plugins.json
If multiple scope entries at different versions exist and the missing skills are exactly those added after the older version, you've hit this bug.
Workaround
Back up installed_plugins.json, drop the stale project-scope entry, restart Claude Code:
cp ~/.claude/plugins/installed_plugins.json{,.bak}
jq '.plugins["X@marketplace"] |= map(select(.scope != "project" or .version != "<old>"))' \
~/.claude/plugins/installed_plugins.json > /tmp/p.json && \
mv /tmp/p.json ~/.claude/plugins/installed_plugins.json
Notes
- Three PRs against the plugin repo (frontmatter length, embedded quotes,
argument-hintremoval) were red herrings — the SKILL.md files were valid the whole time. They are recoverable; the underlying issue was the scope merge. /reload-pluginsreturning0 skillseven when new SKILL.md files exist on disk is the most user-hostile part: it suggests "nothing to refresh" when in fact the loader is silently filtering them out.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗