Plugin cache: a new version dir inherits the previous one's untracked files, replicating them forever
Summary
When a plugin updates, the new cache version directory appears to be seeded from the previous version directory before the fetched content is overlaid. Files that are not part of the plugin at all — untracked, gitignored, or written by a session — are carried into every subsequent version and never collected.
One npm install, run once inside a plugin cache directory in August, had been replicated into 13 version directories by early September: 1.2 GB for a plugin whose actual content is ~100 MB.
Evidence
Each version directory's stray node_modules carries the previous version directory's mtime:
dir dir mtime node_modules mtime
0ad050c57d55 09-03 21:03 09-03 20:12 <- 9293c4174301's dir mtime
9293c4174301 09-03 20:12 09-02 19:50
18305c8e2988 09-02 20:24 09-02 10:50 <- afa75660303d's dir mtime
afa75660303d 09-02 10:50 09-01 16:24 <- 8da88c0b75c8's dir mtime
8da88c0b75c8 09-01 16:24 09-01 15:52
dcf0e39243ff 09-01 16:21 08-29 15:15 <- 55e352c658c7's dir mtime
55e352c658c7 08-29 15:15 08-28 08:55 <- cdd94257f637's dir mtime
cdd94257f637 08-28 08:55 08-25 18:59 <- b1659cee6616's dir mtime (19:12)
b1659cee6616 08-25 19:12 08-23 16:06 <- 1699ab277632's dir mtime
Nine of thirteen chain exactly. The copies are real, not hardlinks — distinct inodes, st_nlink = 1 on both sides of a sampled pair.
The plugin's own remote is clean: node_modules is gitignored, untracked, and absent from the default branch (checked via the contents API). The plugin declares no hooks and no install scripts. So nothing about the plugin explains the directory's presence — only the copy-forward does.
Why it is worth fixing beyond the disk
Anything ever written into a plugin cache directory becomes permanent and self-replicating. A file dropped there once — a scratch artifact, a log, a credential a session wrote while debugging — propagates into every future version of that plugin and survives updates that were expected to replace the tree. The cache stops being a cache and becomes an append-only accumulation, and nothing surfaces it.
Expected
A new version directory contains the plugin's fetched content and nothing else. Either populate it from a clean fetch, or carry forward only paths the plugin declares.
Environment
Claude Code 2.1.261, macOS 15 (arm64). Plugin installed from a marketplace entry with source: { source: "url", url: "…git" }.