[BUG] Plugin manager never reclaims its staging clones — ~/.claude/plugins/cache/temp_* grows without bound (1318 dirs / ~8 GB in 8 days)

Status Fixed / completed
Reported on v2.1.218
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 22, 2026 · closed Aug 25, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

What happened

The plugin manager leaves its staging clones behind in ~/.claude/plugins/cache/ after marketplace operations complete. They are never reclaimed, so they accumulate until the volume fills.

On this machine they grew to 1318 orphaned directories (~8.0 GB) over 8 days and contributed to a full-disk condition (0 bytes free on the data volume).

Where

~/.claude/plugins/cache/ — alongside the real, versioned installs:

~/.claude/plugins/cache/
├── claude-plugins-official/…          ← real install (correct, served from here)
├── temp_git_1784761345113_n8wa6i/     ← orphan
├── temp_subdir_1784761342562_wgulhd.clone/  ← orphan
└── temp_github_…/                     ← orphan

Three prefixes observed: temp_git_*, temp_subdir_*.clone, temp_github_*.
Naming pattern: temp_<kind>_<epoch_ms>_<rand6>.

They are full clones, not scratch files

Each orphan is a complete git working tree with .git and a valid origin:

$ ls -a ~/.claude/plugins/cache/temp_git_1784761345113_n8wa6i
.  ..  .editorconfig  .git  .github  .gitignore  .nvmrc  …
$ git -C … remote get-url origin
https://github.com/obra/claude-session-driver.git

They are referenced by no plugin configuration — the installed plugins are served from the versioned <marketplace>/<version>/ directories, not from these.

It is a leak, not a one-time pile

Timestamps are embedded in the directory names, so the rate is directly measurable:

| Window | Orphans created |
|---|---|
| single 24 h window | 471 |
| peak hour (active use) | 48 |
| another active hour | 35 |

Seven temp_git_* clones were created within 10 milliseconds of each other
(…1345104, …1345106, …1345107, …1345109, …1345111, …1345113, …1345114),
which suggests a parallel fan-out over configured marketplaces (21 configured here).
temp_subdir_* and temp_git_* appear in pairs ~20 ms apart, suggesting both survive the
same code path.

Rate works out to roughly 20–30 directories/hour during active use.

Expected

Staging directories are removed once the marketplace add/update/refresh they belong to
finishes — on the success path, not only on error paths. A stale-staging reclaim on
startup would also cover crashes and interrupted operations.

Actual

They persist indefinitely and accumulate without bound.

Possibly related

#77107 reports AV interrupting a plugin install mid-rename, leaving half-installed
state — same clone-then-rename area, but that issue is about the interrupted path while
this one reproduces on the successful path.

Environment

  • Claude Code 2.1.218
  • macOS (Apple Silicon, APFS)
  • 21 marketplaces configured

Workaround

Deleting ~/.claude/plugins/cache/temp_* is safe once the operations that created them
have finished — they hold no installed-plugin state and every one is re-clonable from its
origin. Worth gating on age and on there being no open file handle, so an in-flight
operation is never touched:

find ~/.claude/plugins/cache -maxdepth 1 -name 'temp_*' -mmin +180

Note for anyone scripting this: the directory mtime is not a reliable age signal —
an interrupted rm -rf updates it, so a half-deleted carcass looks newly created and an
mtime-based filter will skip it forever. The epoch embedded in the directory name is the
immutable creation time and is the better signal.

View original on GitHub ↗

3 Comments

pkumar-cloud · 2 months ago

Corroborating this independently — Claude Code 2.1.220, macOS 26.6 (Apple Silicon) — and adding two things not yet in this thread: a second leak location, a concrete race-condition trigger, and a more severe compounding failure mode.

Second leak location

Same orphaning pattern also happens under ~/.claude/plugins/marketplaces/temp_<epoch_ms>/ (full marketplace clones), not just ~/.claude/plugins/cache/temp_git_* / temp_subdir_*. On this machine right now:

  • ~/.claude/plugins/marketplaces/temp_* — 51 dirs, ~2.0 GB
  • ~/.claude/plugins/cache/temp_git_* — 26 dirs, ~36 MB

Same clone-then-atomic-swap pattern, same "meant to self-delete on success, no reaper otherwise."

Root cause detail: concurrent racing processes, no lock

Reproduced under FORCE_AUTOUPDATE_PLUGINS=1, which triggers a marketplace sync on every single claude invocation. With many concurrent headless/automation sessions running per hour against the same marketplace, processes race to update it — there's no lock. Whichever process loses the race, or is killed/SIGTERM'd mid-clone, leaves its temp clone behind with no cleanup-on-kill and no garbage collection, matching the leak reported above.

More severe compounding failure: .git/index corruption → phantom deletions

Some orphaned clones aren't just inert disk usage — they lose their .git/index file entirely, most likely from being killed at exactly the wrong moment during git's index-write. Once .git/index is gone, git status in that directory reports every tracked file as deleted.

On this machine, three such corrupted orphaned clones showed ~18,500–18,900 phantom "deleted" file entries each — ~56,000+ phantom changes total. This is what surfaced the whole investigation: it showed up in a dev dashboard as "10,000+ git changes," which took real digging to trace back to orphaned plugin-marketplace temp clones rather than an actual working-tree problem.

No native remedy

Confirmed no claude plugin subcommand addresses this: checked claude plugin --help, claude plugin marketplace update --help, and claude doctor — nothing garbage-collects or locks these.

Workaround applied locally

  • Reset FORCE_AUTOUPDATE_PLUGINS to 0 in settings.json (stops the every-invocation sync that was driving the race).
  • Manually reset/restored the 3 corrupted clones.
  • Added a daily launchd job pruning stale temp_* / temp_git_* dirs older than 24h as a stopgap.

Filing this as a report for an upstream fix, not an urgent ask — flagging it here rather than as a separate issue since it's the same underlying gap (clone-then-atomic-swap with no lock and no cleanup-on-kill), just a second leak path plus a more severe compounding failure mode. Given the corruption angle turns "disk growth" into "silently wrong git state that can mislead tooling/dashboards," it may be worth broadening this issue's scope (or cross-linking) to cover: (1) a lock/mutex around marketplace update so concurrent claude processes don't race the same clone, and (2) cleanup-on-kill (e.g., clone into a per-process dir with a SIGTERM/SIGKILL trap, or a startup sweep removing any temp_*/temp_git_* dir whose parent operation is no longer running) — not just an eventual reaper for the success path.

bcherny collaborator · 1 month ago

Thanks for the detailed data — the embedded-epoch rate analysis and the mtime caveat for cleanup scripts were genuinely useful.

I tried to reproduce this on the current release (2.1.233, fresh config directory, git-backed marketplace): marketplace add, marketplace update, and plugin installs from git-subdirectory sources — on both the success and the failure paths — all removed their temp_* staging directories once the operation finished; nothing accumulated in ~/.claude/plugins/cache/.

I also verified the startup reclaim you asked for in "Expected": I planted a stale temp_git_<old-epoch>_* directory in the cache by hand, and it was swept automatically on the next session start. So upgrading to the latest release should both stop the leak and clean up the orphans that have already accumulated (your manual rm of temp_* remains safe in the meantime, as you noted).

This appears to have been fixed in a recent release. Closing — but if you still see temp_* directories accumulating after upgrading to the latest version, please comment and we'll reopen and dig into the remaining path.

🤖 Generated with Claude Code

bealmot · 7 days ago

This still happens on current releases. You asked for a comment if temp_* directories keep accumulating, so here is the data.

Versions: Claude Code 2.1.287 (CLI) and 2.1.289 (the Claude desktop app's bundled Claude Code). Linux (CachyOS, kernel 7.2), btrfs. Both versions ran on this machine since 2026-10-01.

1. The startup reclaim does not sweep old orphans.
Before cleanup, ~/.claude/plugins/cache/ held 4,788 orphan dirs (7.1 GB): 3,121 temp_git_* + 1,667 temp_subdir_*.clone. Most were from 2026-09-14 (2,840) and 2026-09-15 (1,833), which is 23–24 days old. They survived many session starts on 2.1.285–2.1.289. The fix comment says a stale temp_git_<old-epoch>_* dir is swept on the next session start. That did not happen here.

2. New orphans still appear.
After 2.1.287 was installed (2026-10-01), 41 new orphans appeared over 8 days: about 3–10 per day. The newest is from today. Examples (epoch-ms names):

temp_git_1791474608481_x28si5        2026-10-08 10:50 CDT
temp_git_1791443891475_to500l        2026-10-08 02:18 CDT
temp_subdir_<epoch>_<rand>.clone     2026-10-07 22:55 CDT

The clones come from git-backed marketplace sources, e.g. qodo-ai/qodo-skills, greptileai/claude-plugin, obra/superpowers, coderabbitai/skills.

3. Nothing references the orphans.
installed_plugins.json and known_marketplaces.json have 0 references to temp_. All 64 installPath entries point to the real versioned cache dirs. lsof showed no open handles in any temp_* dir. After I trashed every temp_* dir older than 1 day, all 64 plugins still load.

Likely trigger (not proven): the 2026-09-14/15 burst matches many short-lived, concurrent Claude Code processes. On the same days, 66 ~/.claude.json.tmp.<pid>.<hex> files were also left behind (52 of them 0 bytes). This matches a process exit or kill before the staging-dir cleanup runs, plus a startup sweep that does not run or does not match these dirs.

Workaround in use: a weekly user timer moves ~/.claude/plugins/cache/temp_* dirs older than 7 days to the Trash.