Marketplace plugins with undeclared version cache under 'unknown' dir that re-syncs rewrite in place, transiently unregistering components
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(marketplaceclaude-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|disablefor 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
- No version declared →
unknownpath.pr-review-toolkit's.claude-plugin/plugin.jsonhasname/description/authorbut noversion. Ledger (installed_plugins.json) records"version": "unknown",installPath … \pr-review-toolkit\unknown.
- In-place rewrite of the live cache. The cache content dir and its
commands/and.claude-plugin/subdirs were rewritten at2026-07-06T21:24:26–29Z, coinciding with aclaude plugin install/uninstall --scope useron 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.
- Not caused by benign reads (controlled test). Read-only
claude plugin listleftinstalled_plugins.jsonand the cache content dir byte-for-byte unchanged; a headless session touched only the.in_uselease marker. So the churn is tied specifically to sync operations, not normal usage.
- Transient unregistration observed. Immediately after such a rewrite, a fresh headless session enumerated the plugin's skills as
NONE; every subsequent session listedpr-review-toolkit:review-pragain (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)
- 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 literalunknown. Re-sync then writes a new directory and never mutates a live one, matching versioned-plugin behavior. - Atomic swap — sync into a temp dir and
rename()into place; never rewrite files under an active.in_uselease. - No-op unchanged syncs — if the resolved
gitCommitSha/content hash matches what's on disk, skip the rewrite entirely.
Repro (generic)
- Install a marketplace plugin whose
plugin.jsonomitsversion(it caches under…/unknown/); enable at project scope; confirm its command loads from the project root. - Trigger a re-sync — e.g.
claude plugin install <same-plugin> --scope user, or a marketplace update. - Open a new session immediately → the plugin's components are transiently missing while it still shows enabled; open another session → they return.