[BUG] Misleading `plugin-cache-miss` ("not cached at <existing path> — run /plugin to refresh") when the real cause is a missing install record for the current project

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Jul 28, 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 (2.1.220)

What's Wrong?

When a marketplace plugin is enabled in a project's checked-in .claude/settings.json but has no install record for that project in installed_plugins.json (e.g. it was installed with --scope project from a different repo), the /plugin Errors panel reports:

Plugin "workflow" not cached at /Users/me/.cache/claude/plugins/cache/bitwise/workflow/1.0.0
Run /plugin to refresh the plugin cache

Both halves of that message are wrong:

  1. The path exists and is fully populated — including .claude-plugin/plugin.json. Nothing is missing from the cache.
  2. /plugin refresh does not fix it, because the cache was never the problem. The plugin simply has no install record whose scope matches the current project.

The accurate error type for this situation already exists in the codebase — plugin-not-installed, which renders as "Plugin "X" is enabled in project settings but isn't installed — run claude plugin install X@marketplace --scope project" and points at the actual fix — but it is unreachable for the most common kind of plugin (see analysis below).

Users (and coding agents) reading the current message chase phantom cache corruption. In my case the message survived multiple "refresh" attempts across days before I traced it to the record scoping.

Analysis (from the minified 2.1.220 bundle)

In the cache-only plugin loader, for a plugin whose marketplace entry has a string source (a marketplace-relative path like "./plugins/workflow", i.e. the standard marketplace layout):

  • The install record is looked up through a project filter (record matches if scope is user/managed, or projectPath === cwd). When no record matches, the resolved installPath (a) is undefined.
  • The string-source branch then immediately pushes the error — without any existence check ever failing:
if (!u && a && (a.endsWith(".zip") ? await e_(a) : await KVe(a))) c = a;
else if (!u) {
  if (o) i.push({ type: "plugin-cache-miss", source: n, plugin: e.name,
                  installPath: a ?? l ?? t });   // <-- a is undefined here
  return null;
}
  • l is the fallback T.plugins[b]?.find((N) => N.installPath)?.installPath — i.e. any record's install path, including one scoped to a different project. So the error prints a path that exists and validates fine, which is what makes the message so misleading.
  • The guard that produces the accurate plugin-not-installed error is gated on typeof x.entry.source !== "string", so it can never fire for string-source marketplace plugins — the exact plugins the checked-in team-onboarding flow (extraKnownMarketplaces + enabledPlugins) produces.

Steps to Reproduce

  1. Repo A and repo B, both with a checked-in .claude/settings.json declaring the same git marketplace in extraKnownMarketplaces and "my-plugin@my-marketplace": true in enabledPlugins. The marketplace's marketplace.json uses relative-path plugin sources ("source": "./plugins/my-plugin").
  2. In repo A: install the plugin (claude plugin install my-plugin@my-marketplace --scope project). It loads fine there.
  3. Open Claude Code in repo B and check /plugin → Errors.

Actual: Plugin "my-plugin" not cached at <repo A's installPath> — Run /plugin to refresh the plugin cache, where the path exists and is fully populated. Refreshing does not resolve it.

Expected: the existing plugin-not-installed error — "Plugin "my-plugin" is enabled in project settings but isn't installed — run claude plugin install my-plugin@my-marketplace --scope project" — which names the actual cause and the actual fix.

Workaround

claude plugin install my-plugin@my-marketplace --scope user — a user-scope record passes the project filter everywhere, and per-repo enabledPlugins keeps working as the enable/disable switch.

Related issues

The underlying single-record / project-scope limitations are tracked in #73796 (record projectPath ping-pong across repos) and #81706 (dual-scope install collapsing to a project record). This report is specifically about the diagnostics: plugin-cache-miss being emitted with another project's (existing) path, and its "run /plugin to refresh" hint, when the real condition is "no install record for this project". Fixing the message would make those underlying issues self-diagnosing instead of looking like cache corruption.

Environment

  • Claude Code 2.1.220 (Homebrew cask claude-code@latest)
  • macOS (Darwin 27.0.0, arm64)
  • Marketplace: private git repo added via extraKnownMarketplaces, relative-path plugin sources

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗