[Bug or Doc] Session-start plugin reconciliation silently deletes cache subdirectories not in current project's enabledPlugins

Resolved 💬 1 comment Opened Apr 26, 2026 by conor-spec Closed May 28, 2026

Summary

Claude Code's session-start plugin reconciler silently deletes cache subdirectories under ~/.claude/plugins/cache/<marketplace>/ whose plugin id is not in the current project's .claude/settings.json enabledPlugins map. In multi-project setups where projects share a marketplace but enable different subsets of plugins, this produces cache-cycling on every cross-project session boot.

Filing for clarification + documentation: unclear if this is intentional design (just undocumented) or a bug.

Environment

  • Claude Code 2.1.119
  • macOS Tahoe (Darwin 25.3.0)
  • Multi-project setup: Project A enables full plugin set; Project B enables a subset; both reference the same marketplace.

Reproduction

Setup: A marketplace with multiple plugins (e.g., kernel-plugin + feature-plugin-A + feature-plugin-B + feature-plugin-C).

Project A .claude/settings.json enabledPlugins: all four plugins enabled.
Project B .claude/settings.json enabledPlugins: only kernel-plugin + feature-plugin-A.

After installing all four (e.g., from Project A), the cache directory contains:

~/.claude/plugins/cache/my-marketplace/
├── kernel-plugin/
├── feature-plugin-A/
├── feature-plugin-B/
└── feature-plugin-C/

Action: Open claude in Project B (which has the smaller enabledPlugins set). Immediately /exit.

Observed: feature-plugin-B/ and feature-plugin-C/ cache subdirectories are silently deleted at session start by the claude process tree. Captured via fswatch -x -t -r ~/.claude/plugins/cache/<marketplace>/:

Sun Apr 26 11:34:21 2026 .../feature-plugin-B/0.1.0 IsSymLink Removed
Sun Apr 26 11:34:21 2026 .../feature-plugin-B IsDir Removed
Sun Apr 26 11:34:21 2026 .../feature-plugin-C/0.1.0 IsSymLink Removed
Sun Apr 26 11:34:21 2026 .../feature-plugin-C IsDir Removed
[and 13 more for sibling plugins not in Project B's enabledPlugins]

All deletions captured at single second 11:34:21 during session boot. No /plugin uninstall issued, no manual deletion.

Reopening Project A then re-installs/re-symlinks the deleted plugins (16 Created events captured at next session boot).

Expected behavior

Choose one — this is the clarification question:

  1. Cache should persist across project boundaries. Per-project enablement should toggle which plugins LOAD, not delete cache files. Switching projects should not require re-fetching plugins, especially given the marketplace cache appears intended as a global shared store.
  1. Current behavior is intentional but should be documented. The Plugins reference doesn't explicitly cover this lifecycle. A "Plugin scopes and cache lifecycle" section explaining when cache subdirectories get deleted (and why) would set correct expectations.

Impact

  • Performance: every cross-project session boot triggers cache rebuild for non-overlapping plugin sets. For large plugin trees with binary assets this can be significant.
  • Symlink dev-loops break: developers using ln -s <source> <cache-path> to iterate on plugin source lose their dev-loop on every cross-project session boot — the entire cache subdir gets removed (not just the symlink).
  • Operator confusion: /plugin UI may show "installed" while the cache subdir is actively cycling between sessions.
  • Recovery-tooling proliferation: because the deletion is undocumented, downstream operators ship recovery layers (auto-restore hooks, manual reinstall scripts) without realizing the underlying mechanism is per-project gating.

Workaround that worked

Reinstalling kernel-style plugins (ones that should always be available regardless of project context) at scope: "user":

/plugin install <plugin>@<marketplace>
# At install prompt, select "Install for you (user scope)"

scope: "user" entries appear to bypass per-project enabledPlugins reconciliation — once one is registered, the cache subdir survives transitions between projects. Verified by soak-testing across multiple project boundaries with fswatch instrumentation: zero deletion events on user-scoped plugins.

This is workable but feels like a workaround rather than the intended path, especially for plugins that aren't truly user-global (e.g., a kernel/runtime that a small set of projects share).

Adjacent reports

  • #52218 — different but related: installed_plugins.json field desync between reconciliation paths.
  • #15329 (CLOSED) — "Plugin cache prevents per-project installation when globally cached"
  • #26513 (CLOSED) — "Plugin UI shows local-scoped plugin as 'installed' in unrelated projects"
  • #29997 (CLOSED) — "Plugin installed at project scope cannot be reinstalled at user scope from marketplace"

These adjacent issues describe scope/visibility confusion. None explicitly call out the deletion behavior at session start.

Ask

If intentional → please add a "scope and cache lifecycle" section to the Plugins reference. The deletion-vs-persistence rule per scope (user / project / local) and the role of enabledPlugins in the deletion gate would resolve this for operators.

If unintentional → happy to provide more trace data (full fswatch + lsof + ps logs available locally; can share anonymized excerpts on request).

Either way, it would help operators avoid building recovery-layer toolchains for behavior that's actually expected.

View original on GitHub ↗

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