claude plugin has no way to remove a project- or local-scope install record for a path that no longer exists

Status Open
Reported on v2.1.263
Maintainer reply None cached
Activity 0 comments · opened Sep 7, 2026

What happens

A project-scope or local-scope plugin install record in ~/.claude/plugins/installed_plugins.json stores the absolute projectPath it was created from. Once that directory is no longer present, no claude plugin verb can remove the record.

CLI surface, checked on 2.1.263:

  • claude plugin uninstall --help lists --keep-data, --prune, -s/--scope, -y/--yes. There is no path selector. #61578 shows that from a non-matching cwd the local-scope verb reports the plugin is not installed at that scope; #92121 and #73697 show project-scope matching failing in other ways.
  • claude plugin prune --help reports "Remove auto-installed dependencies that are no longer needed", with --dry-run, -s/--scope, -y/--yes. It acts on the dependency axis, not on records keyed by path, and its own -s project has the same cwd-only behavior.
  • claude plugin --help lists no other verb that reaches an install record by path.

Nothing ages the records out either. In the docs, the "Cleaned up automatically" table on the .claude directory page enumerates the paths the cleanupPeriodDays retention sweep deletes, and plugins/ is not among them.

Hand-editing installed_plugins.json is the only remaining option, and that is internal state with no published schema.

Reproduction

Adapted from #61578, where it was verified on Claude Code 2.1.145 / macOS. Not re-run here; only the CLI surface in steps 5 and 6 was rechecked on 2.1.263.

  1. Create a scratch directory outside any git repository and cd into it.
  2. claude plugin install commit-commands@claude-plugins-official -s local
  3. claude plugin list --json shows a record with scope: "local" and projectPath set to the scratch directory.
  4. cd out and delete the scratch directory.
  5. From any other directory, claude plugin uninstall commit-commands@claude-plugins-official -s local fails, because the record cannot be matched from a non-matching cwd. The message points at --scope, but scope is not what disambiguates here.
  6. claude plugin prune -s local --dry-run lists nothing to remove: the record is not an auto-installed dependency.
  7. The record is still in installed_plugins.json.

#61578 reports that recreating the directory and running the uninstall from inside it works for local scope, one cycle per record, and is unavailable when the path was on a volume that is no longer attached. At project scope even that is not dependable: #73697 reports uninstall refusing from inside the project directory.

What I expected

A supported way to remove an install record by the path it names, without recreating that path.

Why it matters

Project-scope records accumulate one per checkout path, and they arrive without an explicit install: a committed enabledPlugins block writes one project-scope record per true entry at session start (#92121). On a machine that uses git worktrees heavily, one installed_plugins.json held 1,005 project-scope records as of 2026-09-04, 755 of which named 12 directories that were no longer present. No CLI verb could reach them. Because the count tracks checkouts rather than deliberate installs, the population that needs removing is exactly the population no verb can reach.

Proposed behavior

Either a path selector on the existing verb:

claude plugin uninstall <plugin>@<marketplace> --scope project --project-path <path>

or a batch form:

claude plugin prune --stale [--dry-run]

that removes project and local records whose projectPath is absent. A dry run matters for the batch form, because an absent path is not proof the checkout is gone. An unmounted volume and an offline network share read the same way as a deleted worktree.

Environment

  • Claude Code 2.1.263, Windows 11. The CLI surface above was checked on this version.
  • Independently reported on macOS in #61578 (Claude Code 2.1.145, 40 stale records across 16 absent paths). #92121 reports the same hand-edit-only cleanup on Windows for records made unreachable by drive-letter casing.

Related

  • #61578 asked for the same capability, proposing either a --projectPath flag on uninstall or a prune --stale batch mode. It was auto-closed as a duplicate of #52831, which reports a different symptom (a stale record shadowing slash-command autocomplete) and is itself closed as not planned. #61578 is locked, so this is filed fresh rather than as a comment there.
  • #92121 covers how these records get created and how --scope project resolves. It does not ask for a path-addressable removal verb.
  • #73697 (open) covers a different removal failure: with the projectPath still present, uninstall and disable guards contradict each other. It does not ask for a path selector.
  • #87666 (open) asks for orphaned project-scope records to be pruned once nothing enables them, again with the path present.
  • #85278 covers duplicate records produced by repeated explicit installs, which is a different way to arrive at records that are hard to remove.
  • Fleet-scale context for this report: melodic-software/claude-code-plugins#3688.

View original on GitHub ↗