[BUG] Claude Desktop - Plugin uninstall does not persist: account-scoped marketplace plugins are silently re-downloaded on next launch, and server-disabled plugins are never removed from disk

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 9, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Environment: Claude Desktop 1.26832.0, macOS (Darwin 25.4.0). Plugin: audit-project v3.7.2, account-scoped marketplace awesome-claude-plugins, installedBy: "user", installationPreference: "available".

Uninstalling a plugin through the desktop UI removes it locally but does not durably record the user's intent. The plugin is fully restored on the next application launch. On the flip side, when deleting fro claude.ai, and the backend does reflect the disable, the plugins shows as disabled, but the local files are never cleaned up - so there is no state in which an uninstalled plugin is actually gone.

Defect 1 - Uninstall writes no tombstone for account-scoped plugins

The uninstall path mutates only local state: it rewrites manifest.json (removing the entry entirely, not tombstoning it) and hard-deletes the plugin directory. No ~/.Trash entry, no staging copy.

The codebase has a purpose-built mechanism to prevent resurrection - a tombstone marker consumed by the sync path via honorTombstone: i !== 'required'. But the tombstone writer is gated on plugin origin:

t === org-provisioned && await o(uninstall_tombstone, ..., () => y.o(e, n.orgUninstalledFile))

Account-scoped marketplace plugins are not org-provisioned, so no tombstone is ever written. The checker looks for a marker the writer never produces for this install path, leaving no durable record that the user removed the plugin.

Defect 2 - Startup reconcile restores the plugin

On launch, RemotePluginManager reconciles local plugins against the backend's account-enabled set. Because the enabled flag was never cleared, the server still reports the plugin as enabled, the reconciler computes a delta, and re-downloads it. Observed across three consecutive restarts:

11:47:05 fetchAccountEnabledPlugins: 3 plugins → Downloaded 2
11:53:38 [user uninstall — nothing logged]
12:00:16 fetchAccountEnabledPlugins: 3 plugins → Downloaded 1 ← restored
~12:02 [user uninstall #2 — nothing logged]
12:03:08 fetchAccountEnabledPlugins: 3 plugins → Downloaded 1 ← restored

Defect 3 - Reconcile is one-directional; disabled plugins are never reaped
RemotePluginManager downloads what is missing but never removes what the backend no longer authorizes. Across every sync observed in a full session, the removal counters were always zero:

Sync complete: N downloaded, 0 removed, 0 orphans cleaned

This held even when the two states disagreed in the removal direction. At 12:10:36 the backend dropped to fetchAccountEnabledPlugins: 2 plugins while three plugins remained on disk.

Combined effect: while backend state lags, uninstall is cosmetic and reverts on restart; once backend state catches up, cleanup never happens. The plugin's code persists on disk either way.

Impact

  • A user who uninstalls a plugin because they do not trust it, has that plugin's code restored to ~/Library/Application Support/ unattended at next launch.
  • No available user action makes the uninstall stick: the local button does not persist, and after backend disable the files are still not removed.

What Should Happen?

Uninstalling a plugin through the UI should be durable and complete:

  1. The removal persists across restarts. Uninstall should either clear the account-enabled flag before reporting success, or write a local tombstone that the startup reconcile honors - the same protection auto_install plugins already get via honorTombstone. A plugin the user removed should not return without an explicit user action.
  2. Backend-disabled plugins are reaped locally. When the account-enabled set no longer contains a plugin, the reconcile should delete its directory and manifest entry, so Sync complete: … N removed reflects real cleanup rather than always reporting 0 removed.
  3. If a removed plugin is intentionally restored by server policy, the user should be informed.

Error Messages/Logs

Steps to Reproduce

  1. Uninstall a plugin via the desktop UI (Settings → Customize → plugins → plugin → Uninstall).
  2. Confirm the local removal. The manifest entry is spliced out and the directory is hard-deleted.
  3. Quit and relaunch Claude Desktop.
  4. Observe the resurrection.

ls $RPM # plugin_<id>/ is back
grep '"name"' $RPM/manifest.json # entry restored

  1. Correlate against the startup sync:

grep -E "fetchAccountEnabledPlugins|RemotePluginManager" ~/Library/Logs/Claude/main.log | tail -20

  1. → Observed: fetchAccountEnabledPlugins: 3 plugins (unchanged from before the uninstall) → Downloaded 1 account-enabled plugin(s) → Sync complete: 1 downloaded, 0 removed (Defects 1 & 2).

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

Claude Desktop 1.26832.0

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Other

Additional Information

_No response_

View original on GitHub ↗