plugin install fails on Windows with EBUSY when other sessions hold cache handles

Resolved 💬 1 comment Opened Apr 22, 2026 by ph47s74x Closed May 28, 2026

Summary

claude plugin install <plugin>@<source> fails reproducibly on Windows when any other Claude Code session is currently running a plugin loaded from ~/.claude/plugins/cache/<source>/<name>/<version>/:

× Failed to install plugin "choir@choir":
  EBUSY: resource busy or locked, rm 'C:\Users\<USER>\.claude\plugins\cache\choir'

Root cause (best reading)

The installer unconditionally rm -rfs ~/.claude/plugins/cache/<source>/ before writing the new version. On Windows, file handles are exclusive — so if even one running session has anything loaded from that directory (e.g. a bun server.ts as a peer MCP), the rm fails and the upgrade aborts.

We verified this during a 0.4.5 → 0.5.0 rollout with 12 concurrent bun.exe processes across 7 Claude Code sessions — the upgrade didn't succeed until sessions naturally cycled off the old version.

Impact

Especially painful for multi-session plugins: the users most likely to have the plugin installed are also the ones most likely to have other sessions holding handles to the cache. The failure is silent about which session is holding a handle, so the only remediation is "close everything and retry."

Proposed shape (stage + swap)

  1. Download new version to cache/<source>/<name>/<version>.staging.<pid>/
  2. Validate (manifest, plugin.json, minimum file set)
  3. Atomically rename …staging.<pid>/…<version>/ (rename on Windows is atomic when src+dst share a volume)
  4. Update version pointer (cache/<source>/plugin.json or equivalent) with a temp-file + rename
  5. Opportunistically GC old versions: walk existing version dirs, attempt rm -rf, on EBUSY leave a .stale-<version> marker and skip
  6. Next install sweeps the marker files once handles have released

New sessions get the new version immediately; existing sessions keep running on the old version until they restart — no disruption, no EBUSY.

Edge cases worth handling

  • Same-version reinstall: hash-compare, delete staging if identical, exit 0
  • Disk full during staging: clean up staging, abort with clear error
  • Interrupted staging: on next install, scan for *.staging.<dead-pid> dirs and prune
  • Concurrent installs: sentinel file cache/<source>/.install.lock.<pid>; wait N seconds, abort if still held

Repro (Windows)

  1. Install any plugin that loads long-running processes from the cache (we see it with choir, but the shape should generalize)
  2. Open 2+ Claude Code sessions that exercise it
  3. In a third session, run claude plugin install <plugin>@<source> targeting an upgrade — EBUSY

Cross-link

Downstream tracking: our downstream tracking issue

View original on GitHub ↗

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