claude plugin uninstall -s local: cannot remove stale entries when projectPath has been deleted

Resolved 💬 2 comments Opened May 22, 2026 by jonathan-kellerai Closed May 26, 2026

claude plugin uninstall -s local selects the registry entry to remove by matching the process CWD against the stored projectPath. When that directory has been deleted (worktree torn down, repo moved, scratch dir trashed), there is no way to remove the stale entry without recreating the missing directory. My current registry: 40 stale entries across 16 dead projectPaths.

Severity: Low for local-filesystem paths (workaround works). Medium for entries on disconnected external volumes — no workaround is available; direct JSON registry editing is the only escape.

---

Actual behavior

Every uninstall from a non-matching CWD fails with:

✘ Failed to uninstall plugin "agent-creator@kellerai-dev-marketplace": Plugin "agent-creator@kellerai-dev-marketplace" is not installed in local scope. Use --scope to specify the correct scope.

Two problems with this message:

  • The plugin is in the registry at local scope (plugin list --json confirms it). The CLI just can't match it from the wrong CWD.
  • The hint Use --scope is wrong. -s local was already passed. The disambiguator is CWD, not scope.

No flag in claude plugin uninstall --help overrides CWD-based disambiguation (--keep-data, --prune, -s/--scope, -y/--yes — no --projectPath). claude plugin prune --help (--dry-run, -s/--scope, -y/--yes — no --stale) only handles auto-installed dependencies, not stale projectPath entries.

---

Expected behavior

A first-class path to remove a stale -s local entry without recreating the missing directory — either a --projectPath flag on uninstall or a prune --stale batch mode. Details in Proposed fixes below.

The same CWD-disambiguation applies to claude plugin disable -s local and claude plugin update -s local. Any fix should cover all local-scope operations.

---

<details>
<summary>▸ Reproduction (verified on claude 2.1.145, macOS Darwin 25.3.0)</summary>

Use a plugin NOT enabled at project scope — a project-scope conflict produces a different error and obscures this bug.

mkdir -p /tmp/repro-stale && cd /tmp/repro-stale
claude plugin install agent-creator@kellerai-dev-marketplace -s local

Confirm the entry was recorded (note: macOS resolves /tmp/private/tmp at store time):

claude plugin list --json | jq '[.[] | select(.projectPath != null) | select(.projectPath | endswith("repro-stale"))]'

Delete the directory:

cd / && trash /tmp/repro-stale

Attempt removal from three different CWDs — all fail, entry persists:

cd ~ && claude plugin uninstall agent-creator@kellerai-dev-marketplace -s local -y
cd /tmp && claude plugin uninstall agent-creator@kellerai-dev-marketplace -s local -y
cd /var/empty && claude plugin uninstall agent-creator@kellerai-dev-marketplace -s local -y

</details>

<details>
<summary>▸ Current workaround</summary>

For entries on the local filesystem:

  1. Find the exact projectPath from claude plugin list --json.
  2. mkdir -p <projectPath> && cd <projectPath>
  3. claude plugin uninstall <plugin>@<marketplace> -s local -y
  4. cd / && trash <projectPath>

Side-effect: step 3 seeds <projectPath>/.claude/settings.local.json with {"enabledPlugins":{}}. Step 4 removes it. No state lingers, but the dance is tedious at scale (40 entries = 40 cycles).

Last resort (unmounted-volume entries): When the dead projectPath is on a disconnected volume, mkdir cannot recreate it. Edit ~/.claude/plugins/installed_plugins.json directly: back it up (.bak2/.bak3 backups exist), remove the stale object, and restart Claude Code.

</details>

<details>
<summary>▸ Proposed fix — Option A: --projectPath flag on uninstall</summary>

Add --projectPath <path> to claude plugin uninstall — a registry selector that overrides CWD-based disambiguation for -s local:

claude plugin uninstall -s local <plugin>@<marketplace> --projectPath /path/to/dead/dir -y

Zero filesystem side effects. Mirrors npm uninstall --prefix <path>. Smallest possible surface change.

Also fix the misleading error: when a local-scope entry exists but CWD doesn't match, say Use --projectPath instead of Use --scope.

</details>

<details>
<summary>▸ Proposed fix — Option B: prune --stale batch mode</summary>

Extend claude plugin prune with --stale — scan local-scope entries, stat each projectPath, remove entries where the directory is absent:

claude plugin prune --scope local --stale --dry-run
claude plugin prune --scope local --stale -y

Handles the 40-entry case in one call. Analogous to git gc --prune. Composes with --dry-run.

Implementation note: a --require-git sub-flag can't inspect the now-missing directory. If this filtering is needed, consider recording hadGit: bool in the registry at install time. The implementation should also confirm none of the to-be-pruned entries are providers of active plugins before removing them.

Options A and B are complementary — Option A for surgical removal, Option B for scheduled gc.

</details>

---

Environment

  • Claude version: 2.1.145 (Claude Code)
  • OS: macOS Darwin 25.3.0 arm64
  • Reproduction verified on macOS only. On Linux /tmp is not a symlink — path resolution differs. Windows untested.
  • Registry at time of report: ~316 total entries, ~234 at local scope, 40 entries across 16 stale projectPaths

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗