[BUG] Per-version plugin cache dirs accumulate without reliable GC
Plain-language summary
What I noticed. My ~/.claude/plugins/cache/ directory keeps growing. Every plugin update writes a new versioned directory and leaves the previous ones behind. Sometimes Claude Code marks superseded versions as orphaned (writes a .orphaned_at file), sometimes it doesn't — with no apparent pattern from the outside. The unmarked ones are never cleaned up by anything.
What the system can't do. Reliably reclaim disk after a plugin update. The orphan-marking pass is non-deterministic, and there is no documented reaper that processes the markers it does write. Once a directory misses the marker, it is stranded indefinitely. Symlink-installed entries (a local-dev override pattern) get stranded the same way once the referencing scope stops being opened.
What that means for me as a user. My disk fills silently. For plugins with large content — the one I tracked closely is ~1.8 GB per version — I lose multiple gigabytes per week of churn to dead old versions. There is no built-in command that reclaims this: /plugin uninstall removes the registry entry but does not always clear the cache directory. To recover space I have to manually rm -rf version directories, which is unsafe — delete the wrong one and a project install breaks. I should not have to manage Claude Code's cache by hand.
---
What's Wrong?
Every /plugin install and /plugin update writes a new directory at ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/. Old versioned directories are not reliably cleaned up after they are superseded.
There appears to be a deferred orphan-marker mechanism (Claude Code writes .orphaned_at sentinel files into some superseded version dirs and presumably reaps them later), but the marking pass is incomplete. In my plugin cache I have repeatedly observed superseded sibling directories that were never orphan-marked, sitting alongside the active version 24+ hours after they were superseded.
Net effect for any active user of the plugin system: the plugin cache grows monotonically. On my machine, one plugin's cache subtree has accumulated stranded version directories across multiple update cycles — most cleanly documented in a snapshot from earlier this month (~255 MB across four superseded version dirs for a single plugin; details below).
This affects every Claude Code user with extraKnownMarketplaces, not just dev-override workflows.
What Should Happen?
When a plugin is updated (or reinstalled at a different version):
- The new versioned cache dir is created and the registry swapped to point at it.
- All sibling versioned dirs for the same
<marketplace>/<plugin>/that are no longer referenced by any registry entry should be either deleted immediately or.orphaned_at-marked and reaped on a defined cadence (e.g., next session start, or N days).
The bug is that step 2 is incomplete: some superseded siblings get the marker, others don't, and the ones without the marker are never reaped.
Steps to Reproduce
- Install a plugin:
/plugin install build-loop@rosslabs-ai-toolkit. Observe a fresh~/.claude/plugins/cache/rosslabs-ai-toolkit/build-loop/<v1>/directory. - Bump the plugin to a new version (publish a new release on the marketplace, or use
/plugin update). Observecache/.../<v2>/is created and the registry'sinstallPathswaps to it. - Repeat through several version bumps over a few days.
- Run:
``bash``
ls -la ~/.claude/plugins/cache/<marketplace>/<plugin>/
find ~/.claude/plugins/cache/<marketplace>/<plugin>/ -name '.orphaned_at'
du -sh ~/.claude/plugins/cache/<marketplace>/<plugin>/
- Expected: only the actively-referenced version dir is present, OR all superseded ones carry an
.orphaned_atmarker (and a documented reaper removes them later).
Actual: superseded version directories are still present, mixed — some with .orphaned_at, some without — and the unmarked ones accumulate indefinitely.
Error Messages/Logs
No errors. Silent disk growth.
Concrete evidence from my machine
Primary evidence — snapshot from 2026-05-03, documented in a shim I wrote to compensate:
At that point, one plugin's cache subtree (~/.claude/plugins/cache/rosslabs-ai-toolkit/build-loop/) contained four superseded version directories: 0.6.0, 0.12.0, 0.12.1, 0.12.11. Total occupied: ~255 MB. Of the four:
0.6.0,0.12.0,0.12.1each carried an.orphaned_atmarker.0.12.11did not carry an.orphaned_atmarker, despite being a clearly-superseded sibling of the then-active version0.12.10for more than 24 hours.
That is the bug in one shot: the orphan-marking pass is non-deterministic. Marked entries can in principle be reaped on a cadence; unmarked ones never will be, and there is no logged reason for the divergence.
The 2026-05-03 snapshot has since been cleaned up by a shim I wrote, so the directories named above are no longer on disk.
Secondary evidence — current state (verified 2026-05-23):
$ ls -la ~/.claude/plugins/cache/rosslabs-ai-toolkit/build-loop/
drwxr-xr-x 5 ... May 23 02:11 .
drwxr-xr-x 19 ... Apr 20 18:46 ..
lrwxr-xr-x ... May 5 12:45 0.10.0 -> /Users/tyroneross/dev/git-folder/build-loop
lrwxr-xr-x ... May 22 23:46 0.12.10 -> /Users/tyroneross/dev/git-folder/build-loop
drwxr-xr-x 50 ... May 23 02:11 0.12.12 (real dir, 1.8 GB)
$ find ~/.claude/plugins/cache/rosslabs-ai-toolkit/build-loop/ -name '.orphaned_at'
(no output)
Registry references for the same plugin in ~/.claude/plugins/installed_plugins.json:
0.12.12(user-scoped) — active.0.12.12(project-scoped,/Users/tyroneross) — active.0.12.10(project-scoped,/Users/tyroneross/Documents/Codex/2026-05-01/install-v0-sdk-so-i-can) — still active for that one project. The0.12.10symlink is therefore legitimately referenced.0.10.0— not referenced by any registry entry. It is a true strand.
So the current cache contains one clear strand (the 0.10.0 symlink) and one stale-but-still-referenced symlink (0.12.10, referenced only by a project that hasn't been opened to re-trigger an install since). The 0.10.0 entry has no .orphaned_at marker, consistent with the 2026-05-03 observation that the marking pass is incomplete.
The two snapshots together demonstrate the bug shape:
- Inconsistent marking (some superseded siblings get
.orphaned_at, others don't). - No cleanup of older symlink installs that are no longer referenced anywhere in the registry.
- Net: monotonic growth in the absence of an external GC. The compounding scale depends on how often the user updates plugins; for a plugin that releases daily and has multi-hundred-MB content, this becomes gigabytes in a few weeks.
Claude Model
Not relevant (plugin-manager bug, model-independent).
Is this a regression?
I don't know. I have only had extraKnownMarketplaces configured since April 2026 and have observed the accumulation throughout that window.
Last Working Version
Unknown.
Claude Code Version
2.1.149 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
- Suggested fix shape: on successful
/plugin installand/plugin update, enumeratecache/<marketplace>/<plugin>/*and.orphaned_at-mark any sibling not currently referenced by the registry. Treat the marking as the single source of truth and reap on a defined cadence (next session start is reasonable). - Symlink-installed entries should be detected and either left alone (with a warning) or replaced atomically by the new install, not stranded.
- Happy to provide before/after disk-usage data over a longer window if useful.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗