Plugin cache: orphaned versions never deleted because .in_use leases from dead PIDs are never reaped

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Jul 29, 2026

What happens

The documented 14-day orphan cleanup for plugin cache versions never fires, because orphaned versions stay pinned by .in_use/ lease files belonging to long-dead processes. Nothing reaps a lease whose owning process has exited, so a version touched by a session that died without releasing is pinned permanently.

plugins-reference says:

Each installed version is a separate directory in the cache. When you update or uninstall a plugin, the previous version directory is marked as orphaned and removed automatically 14 days later. The grace period lets concurrent Claude Code sessions that already loaded the old version keep running without errors.

The marker is written correctly. The deletion never happens.

Evidence

~/.claude/plugins/cache on this machine: 33 versions, 114 MB, 1948 lease files, 1629 of them (84%) held by PIDs that no longer exist.

Seven versions are orphan-marked well past the 14-day grace period and still present:

| plugin | version | orphaned | leases | dead PIDs |
|---|---|---|---|---|
| claude-plugins-official/superpowers | 6.0.2 | 39d ago | 5 | 5 |
| nhangen-tools/obsidian | 1.7.0 | 38d ago | 5 | 5 |
| claude-plugins-official/figma | 2.2.50 | 36d ago | 5 | 5 |
| claude-plugins-official/github | (unknown) | 29d ago | 5 | 5 |
| nhangen-tools/domain-glossary | 0.2.0 | 28d ago | 5 | 5 |
| nhangen-tools/manus-dispatch | 0.1.3 | 25d ago | 5 | 5 |
| nhangen-tools/context-loop | 0.1.2 | 24d ago | 5 | 5 |

Two things make this specific rather than general cache growth:

1. It is the same five dead PIDs in every case. All seven versions are pinned by exactly 2416, 5092, 63462, 63464, 93464. Five sessions died in mid-June and permanently pinned every plugin version they had loaded. Verified dead with kill(pid, 0)ProcessLookupError for all five.

2. Every pinning lease is a never-renamed temp file. In all seven cases the leases are named <pid>.tmp.<hash> — 100% of them — while live versions hold mostly bare <pid> leases. On nhangen-tools/obsidian the current version's .in_use/ holds 182 bare-pid leases and 1 .tmp.. So the write-to-temp-then-rename for a lease appears to leave the temp behind when the process dies mid-write, and the temp counts as a valid pin.

The lease already contains what a reaper needs

$ cat ~/.claude/plugins/cache/nhangen-tools/obsidian/1.7.0/.in_use/2416.tmp.a73eb402
{"pid":2416,"procStart":"Thu Jun 18 00:44:45 2026"}

pid plus procStart is exactly the pair needed to check liveness safely, including guarding against PID reuse. The data is recorded and then never consulted.

Secondary: leases accumulate without bound even on live versions

nhangen-tools/obsidian 1.16.1 — not orphan-marked, so considered live — holds 183 lease files aged 0 to 11 days. Sessions are not releasing on exit, so the count only grows. This is what makes the pinning problem compound: the longer a version stays current, the more stale leases it accrues to be pinned by later.

Expected

A version orphan-marked more than the grace period ago should be deleted when no lease belongs to a live process. Concretely, before honoring .in_use/:

  1. Drop leases whose pid does not exist, or exists with a procStart that does not match the recorded one (PID reuse).
  2. Treat *.tmp.* leases as invalid pins, or clean them up on the next successful lease write.
  3. Then apply the 14-day rule to what remains.

Environment

  • Claude Code 2.1.220
  • macOS 26.5.2 (arm64)

Related

#16453, #37865, #47966 are all closed, presumably by the orphan-marker mechanism. This report is about why that mechanism does not complete: the liveness check on the lease directory has no reaper behind it. claude plugin prune does not help — it removes auto-installed dependencies, not orphaned versions.

View original on GitHub ↗

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