[BUG] plugin install still fails with EPERM on locked-file rename and leaves temp_local_* behind on Windows (2.1.220, despite the v2.1.186 fix)
Summary
claude plugin install intermittently fails on Windows with EPERM: operation not permitted, rename when moving the staging directory into the plugin cache, and leaves the temp_local_* directory behind.
Both of these are what the v2.1.186 changelog entry says was fixed:
Fixed plugin cache writes leaving temp files behind on failure and failing on locked-file renames on Windows and network filesystems
I'm on 2.1.220 and still see both, so the fix looks incomplete rather than absent.
Environment
- Claude Code 2.1.220 (native Windows install,
%USERPROFILE%\.local\bin\claude.exe) - Windows 11 Enterprise 26200
- Microsoft Defender real-time protection + a corporate proxy agent, both scanning on write
- Plugin from a private GitHub-source marketplace, ~78 files
Reproduction
Failure is intermittent — 1 failure in 6 attempts in a controlled loop. Each iteration removed the versioned cache dir first, to force a fresh clone + rename:
for ($i = 1; $i -le 6; $i++) {
Remove-Item "$HOME\.claude\plugins\cache\<marketplace>\<plugin>" -Recurse -Force
claude plugin install <plugin>@<marketplace>
"attempt $i exit=$LASTEXITCODE"
}
attempt 1 : exit=0
attempt 2 : exit=0
attempt 3 : exit=0
attempt 4 : exit=0
attempt 5 : exit=0
attempt 6 : exit=1 <-- failed
Exact error (attempt 6)
✘ Failed to install plugin "<plugin>@<marketplace>": EPERM: operation not permitted, rename
'C:\Users\<USER>\.claude\plugins\cache\temp_local_1785186411515_vjc8mk'
-> 'C:\Users\<USER>\.claude\plugins\cache\<marketplace>\<plugin>\1.40.1'
Two distinct problems
1. The rename still fails. Presumably the AV/scanner still holds a handle on the just-cloned tree when the rename fires. It reproduces far more readily on a large plugin (~78 files); a small plugin (~6 files) in the same marketplace has never failed for us — consistent with a scan-duration race.
2. temp_local_* is left behind on failure.
temp_local_1785186411515_vjc8mk <-- still in plugins/cache/ after the failure
These accumulate silently and are never cleaned up.
Why this matters more than an occasional retry
A failed install de-registers the plugin from both registries — it disappears from installed_plugins.json and from enabledPlugins in settings.json. The result is not "install didn't happen", it's the previously-working plugin is now gone, and for a plugin that ships MCP servers the symptom is an empty /mcp with no obvious cause. We lost ~20 minutes to exactly this, misdiagnosing it as a Docker problem, because a disabled plugin's servers are absent from /mcp rather than shown as failed.
It's worse in combination with the documented cache workaround for #14061: any flow that clears the cache and reinstalls has a real per-run chance of leaving a plugin uninstalled.
Mitigating detail (possibly useful)
An immediate retry of the identical command usually succeeds — the staging dir has gone cold by then, so the scanner has released it. That suggests an internal retry-with-backoff around the rename would resolve most of these. Ordinary fs.rename on Windows fails immediately if any handle is open; a couple of retries ~100–500ms apart is the usual remedy.
Expected
- The rename retries briefly before failing, rather than failing on first contention.
- On genuine failure,
temp_local_*is cleaned up. - A failed install does not leave the plugin removed from
enabledPlugins— ideally the previous working state is preserved, or at minimum the error says the plugin is now uninstalled so the user knows to re-run.
Related
- #14061 —
/plugin updatedoes not invalidate the plugin cache (open). The documented workaround is to clear the cache and reinstall, which is what exposes this bug repeatedly. - #17361 — plugin cache never refreshes;
autoUpdatedoesn't update what Claude reads (open).