[BUG] Project-scoped plugin records overwritten across projects (installed_plugins.json reconcile rewrites the first record instead of upserting by projectPath)

Status Open
Reported on v2.1.221
Maintainer reply None cached
Activity 2 comments · opened Aug 4, 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?

When the same plugin@marketplace is enabled at project scope by two or more projects that live at different paths — this includes both independent repositories that each ship the plugin in their committed .claude/settings.json, and (very commonly) two checkouts of the same repository at different absolute paths — the single global ~/.claude/plugins/installed_plugins.json cannot retain both install records.

On session start, the settings→installed_plugins.json reconciliation appears to rewrite an existing project-scoped record in place to point at the currently-open project, rather than keeping a separate record per project path. A follow-up dedup then discards the collision. Net effect: project-scoped plugin "homes" ping-pong between projects — whichever project you opened most recently owns the record, and the others report the plugin as errored / not-installed until their own next reconcile.

This is inconsistent with how records are otherwise handled: the applicability/read path and the installer both key on the project path (scope + projectPath), and the on-disk schema stores an array of records per plugin — so multiple project homes are representable everywhere except in this reconciliation step.

Related: #83758 — same root-cause family (global, project-path-keyed state breaking across multiple checkouts of one repository), different file (~/.claude.json / auto-memory vs installed_plugins.json).

What Should Happen?

Each project that enables a plugin at project scope should keep its own (project, <that project path>) install record. Opening one project must not mutate or delete another project's record. (A likely direction: have the reconciler upsert the record matching the current project path instead of editing the first record in the array — bringing it in line with the read path and installer, which already key on project path.)

Error Messages/Logs

No crash. Observable symptoms:

  • ~/.claude/plugins/installed_plugins.json: a project-scoped plugin's projectPath flips to the current project each time you switch projects; the previous project's record is gone (not added alongside).
  • Running /plugin in the project that just lost its record shows its project-scoped plugins in an errored/not-loaded state until that project re-reconciles (a fresh session there, or /reload-plugins).

Verified before/after on 2.1.221 — all project-scoped records flipped from one checkout's path to the other's, and the record count per plugin stayed at 1 (overwrite in place, not append):

BEFORE (session opened in checkout A):  <plugin> → [{ scope: project, projectPath: .../checkout-A }]   (1 record)
AFTER  (session opened in checkout B):  <plugin> → [{ scope: project, projectPath: .../checkout-B }]   (1 record)

Plugins that also had a user-scope record kept both (a user record has no projectPath to collide on), consistent with the described mechanism.

Steps to Reproduce

  1. Two working directories at different absolute paths — either two independent repos, or two checkouts/worktrees of the same repo (~/work/repoA, ~/work/repoB).
  2. Each commits the same entry in .claude/settings.json:

``json
{ "enabledPlugins": { "code-review@claude-plugins-official": true } }
``

  1. Open Claude Code in repoA; observe in installed_plugins.json:

code-review@… → [{ scope: "project", projectPath: ".../repoA" }].

  1. Open Claude Code in repoB; the same single record is now

[{ scope: "project", projectPath: ".../repoB" }] — the repoA record is gone, not added alongside.

  1. Back in repoA, run /plugin: its project-scoped plugins report errors until repoA re-reconciles.

Impact: This is not an edge configuration. It triggers for any developer who (a) keeps multiple checkouts/worktrees of one repo (a routine practice), or (b) works across several independent repos that each enable a common plugin (formatters, code-review, commit-commands, etc.) via committed project settings. Project scope specifically exists to confine heavy plugins — browser-driving MCP servers, multi-skill bundles, language servers — to the few repos that need them; the usual "enable it at user scope" workaround defeats that by loading the plugin's full cost into every project.

Is this a regression?

Not sure.

Claude Code Version

2.1.221

Platform

Claude Code CLI (terminal), native install

Operating System

macOS

View original on GitHub ↗

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