Plugin UI shows local-scoped plugin as 'installed' in unrelated projects, blocks reinstall with different scope
Bug Report
Version: Claude Code 2.1.45 (latest as of 2026-02-17)
OS: macOS 26.3 (Darwin 25.3.0)
---
Summary
When a plugin is installed with scope: "local" for Project A, the /plugin UI shows it as "installed" (green checkmark) when browsing from Project B (an unrelated project). This is misleading because the plugin does not actually load in Project B. Worse, the "installed" state prevents the user from reinstalling it with a different scope (e.g., user), effectively locking them out.
Additional issue: Even removing the marketplace entirely and re-adding it does NOT fix the problem. The installed_plugins.json record persists independently of marketplace state, so the plugin continues to show as "installed" with no UI path to reinstall it. The only workaround is manually editing the JSON file.
---
Steps to Reproduce
- Open Claude Code in Project A (
/path/to/project-a/) - Run
/plugin→ browse a marketplace → install a plugin (e.g.,compound-engineering@every-marketplace) - When prompted for scope, choose "Install for you, in this repo only (local scope)"
- The plugin installs successfully. Verify:
installed_plugins.jsongets a record with"scope": "local"and"projectPath": "/path/to/project-a/"project-a/.claude/settings.local.jsongets"enabledPlugins": { "compound-engineering@every-marketplace": true }
- Close Claude Code
- Open Claude Code in Project B (
/path/to/project-b/) — a completely unrelated project - Run
/plugin→ browse the same marketplace - Observe: the plugin shows
✔ compound-engineering (installed)with a green checkmark - Try to use a command from the plugin (e.g.,
/workflows:brainstorm) → fails with "Unknown skill" - Try to install the plugin again for Project B or as user-scope → cannot, because the UI treats it as already installed
- Try removing the marketplace and re-adding it → the plugin still shows as "installed" because the record lives in
installed_plugins.json, not in the marketplace data. There is no UI path to clear this state.
---
Expected Behavior
- In Project B, the plugin should show as not installed (radio button, not checkmark), since it is only locally scoped to Project A
- Alternatively, if showing it as "installed", the UI should indicate the scope (e.g.,
"installed in /path/to/project-a/ only") and offer an option to install it for the current project or globally - The user should be able to install the same plugin with a different scope without manual JSON editing
- Removing and re-adding a marketplace should allow reinstallation, or at minimum the UI should provide a "reinstall" or "change scope" option
---
Actual Behavior
- The plugin shows as "installed" in all projects, regardless of scope
- The plugin does not load (commands/skills are unavailable) because
enabledPluginsis only in Project A'ssettings.local.json - There is no UI path to reinstall with a different scope — the user is stuck
- Removing the marketplace and re-adding it does NOT reset the "installed" state — the user is completely locked out without manual JSON editing
---
Root Cause Analysis
The issue is in the r0() / EH9() functions that determine whether a plugin is installed:
// r0(pluginId) — "is installed?"
function r0(A) { return EH9(A) !== void 0 }
// EH9(pluginId) — get install info
function EH9(A) {
let K = n0().plugins[A]; // reads from installed_plugins.json
if (!K || K.length === 0) return;
let Y = K[0]; // ← takes the FIRST record, ignores scope/projectPath
if (!Y) return;
return { version: Y.version, installedAt: Y.installedAt, ... }
}
EH9() returns the first record from the installed_plugins.json array for that plugin — it does not filter by scope or compare projectPath against the current working directory (CA()).
Meanwhile, the scope-aware function nZ6(pluginId) does exist and correctly matches against CA():
function nZ6(A) {
let K = n0().plugins[A];
let Y = CA(); // current project path
let z = K.find(H => H.scope === "local" && H.projectPath === Y);
if (z) return { scope: z.scope, projectPath: z.projectPath };
let w = K.find(H => H.scope === "project" && H.projectPath === Y);
if (w) return { scope: w.scope, projectPath: w.projectPath };
let $ = K.find(H => H.scope === "user");
if ($) return { scope: $.scope };
return { scope: K[0].scope, projectPath: K[0].projectPath };
}
But nZ6() is not used by the UI's "is installed?" check — only r0() / EH9() are.
Why removing the marketplace doesn't help: The marketplace removal only clears marketplace metadata (known_marketplaces.json and the marketplaces/ directory). The installed_plugins.json records are independent and persist. Since r0() reads from installed_plugins.json, the plugin continues to appear as "installed" even after the marketplace is removed and re-added.
---
Suggested Fix
r0(pluginId) should be scope-aware. A plugin should only be considered "installed" for the current context if:
- It has a record with
scope: "user"(global — always counts), OR - It has a record with
scope: "project"orscope: "local"whereprojectPath === CA()(matches current directory)
Something like:
function r0(A) {
let K = n0().plugins[A];
if (!K || K.length === 0) return false;
let Y = CA();
return K.some(H =>
H.scope === "user" ||
H.scope === "managed" ||
((H.scope === "project" || H.scope === "local") && H.projectPath === Y)
);
}
Additionally, the plugin details UI (QZ6) could show scope-specific install options when the plugin is installed elsewhere but not in the current project.
---
Workaround
Manually edit ~/.claude/plugins/installed_plugins.json:
- Find the plugin entry (e.g.,
"compound-engineering@every-marketplace") - Change
"scope": "local"to"scope": "user" - Remove the
"projectPath"field - Restart Claude Code
Or add "compound-engineering@every-marketplace": true to ~/.claude/settings.json under enabledPlugins to enable it globally.
Note: There is no way to fix this through the UI alone — removing the marketplace and re-adding it does not help.
---
Related State
installed_plugins.json record (problematic):
"compound-engineering@every-marketplace": [
{
"scope": "local",
"projectPath": "/path/to/project-a",
"installPath": "~/.claude/plugins/cache/every-marketplace/compound-engineering/2.27.0",
"version": "2.27.0",
"installedAt": "2026-01-21T21:12:46.796Z"
}
]
Project A's .claude/settings.local.json (correct — has enabledPlugins):
{
"enabledPlugins": {
"compound-engineering@every-marketplace": true
}
}
Project B's .claude/settings.local.json (missing — no enabledPlugins for this plugin):
The plugin is not listed here, so it never loads, but the UI still shows it as "installed".
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗