Marketplace plugins with undeclared version cache under 'unknown' dir that re-syncs rewrite in place, transiently unregistering components

Open 💬 0 comments Opened Jul 7, 2026 by CloudSourcer

Summary

Marketplace plugins whose plugin.json omits a version field are cached under a single literal unknown/ directory (~/.claude/plugins/cache/<marketplace>/<plugin>/unknown/) instead of a version-stamped, immutable directory. Because that path is shared across every build/scope, any sync operation re-writes the live cache in place — and a session that (re)loads during or immediately after that rewrite transiently fails to register the plugin's components (skills / commands / agents). The plugin still reports enabled/green in claude plugin list and Manage Plugins, so the failure looks like a phantom.

Environment

  • Claude Code 2.1.202
  • Windows 11, VS Code extension + integrated PowerShell terminal
  • Example plugin: pr-review-toolkit@claude-plugins-official (marketplace claude-plugins-official)
  • Also affected on this machine (all cache under unknown/): commit-commands, context7, plugin-dev, skill-creator

Root cause

Version-stamped plugins cache at <plugin>/<version>/ — a new, immutable directory per version (e.g. azure/1.1.75/). Plugins that don't declare a version cache at <plugin>/unknown/. That unknown/ path is reused for every install/update/scope, so it is mutated in place on:

  • claude plugin install|uninstall|update|enable|disable for that plugin at any scope (even a scope other than the one currently in use), and
  • marketplace re-sync.

When the cache files under unknown/ are rewritten while a session holds them (or a new session initializes mid-rewrite), the component registry momentarily comes up empty for that plugin.

Evidence

  1. No version declared → unknown path. pr-review-toolkit's .claude-plugin/plugin.json has name/description/author but no version. Ledger (installed_plugins.json) records "version": "unknown", installPath … \pr-review-toolkit\unknown.
  1. In-place rewrite of the live cache. The cache content dir and its commands/ and .claude-plugin/ subdirs were rewritten at 2026-07-06T21:24:26–29Z, coinciding with a claude plugin install/uninstall --scope user on the same plugin — i.e. a different-scope operation rewrote the shared live cache the project-scope session was using. Version-stamped plugins never exhibit this because a new version writes a new directory.
  1. Not caused by benign reads (controlled test). Read-only claude plugin list left installed_plugins.json and the cache content dir byte-for-byte unchanged; a headless session touched only the .in_use lease marker. So the churn is tied specifically to sync operations, not normal usage.
  1. Transient unregistration observed. Immediately after such a rewrite, a fresh headless session enumerated the plugin's skills as NONE; every subsequent session listed pr-review-toolkit:review-pr again (3/3). Classic cold-cache flap.

Impact

User-facing symptom is "plugin enabled/green but its slash command is missing," which survives a single restart and only clears on a second session launch. It is very hard to diagnose because claude plugin list / Manage Plugins report the plugin enabled regardless of whether components actually registered. This cost a multi-hour investigation on our side (tracked downstream as our internal issue #350).

Related issues

Same unknown/ cache directory, different symptom (both closed):

  • #39140 — Plugin cache unknown/ directory loses execute permissions on shell scripts
  • #38721 — Plugin cache unknown/ directory created without executable permissions on hook scripts

This report is a distinct symptom (in-place re-sync → transient component unregistration) of the same underlying unknown/-dir design.

Suggested fixes (any one closes the class)

  1. Stamp undeclared-version plugins immutably — use the marketplace gitCommitSha (already stored in the ledger) or a content hash as the cache dir name instead of the literal unknown. Re-sync then writes a new directory and never mutates a live one, matching versioned-plugin behavior.
  2. Atomic swap — sync into a temp dir and rename() into place; never rewrite files under an active .in_use lease.
  3. No-op unchanged syncs — if the resolved gitCommitSha/content hash matches what's on disk, skip the rewrite entirely.

Repro (generic)

  1. Install a marketplace plugin whose plugin.json omits version (it caches under …/unknown/); enable at project scope; confirm its command loads from the project root.
  2. Trigger a re-sync — e.g. claude plugin install <same-plugin> --scope user, or a marketplace update.
  3. Open a new session immediately → the plugin's components are transiently missing while it still shows enabled; open another session → they return.

View original on GitHub ↗