/plugin install fails on Windows with EBUSY rename errors (Defender real-time scan race)
Bug: /plugin install fails on Windows with EBUSY rename errors (Defender real-time scan race)
Environment
- OS: Windows 11 Enterprise (10.0.26100)
- Claude Code: CLI on Windows (PowerShell)
- Windows Defender real-time protection: ON (enterprise-managed)
- User privileges: non-admin (cannot add a Defender exclusion or disable real-time scanning)
- Corporate network: outbound SSH (port 22) blocked
Summary
Installing a marketplace plugin on locked-down enterprise Windows fails repeatedly. Two distinct issues compound:
- SSH transport is assumed.
/plugin installclones plugin repos overgit@github.com:(SSH/port 22). On corporate networks that block port 22 this fails withssh: connect to host github.com port 22: Connection timed out. HTTPS (443) works fine. - Clone-then-rename + git checkout race against Defender. The installer clones into a temp dir then performs an atomic rename into the cache, and git checkout uses
HEAD.lock→HEADrenames. On Windows, a rename fails withEBUSYwhen any process holds a handle on the file — and Defender's real-time scanner grabs handles on freshly-written files. This produces intermittent failures at different steps:
EBUSY: resource busy or locked, rename '...temp_github_*' -> '...cache\<marketplace>\<plugin>\<ver>'Failed to checkout commit <sha>: fatal: update_ref failed for ref 'HEAD': couldn't set 'HEAD'
The same class of failure also hit /plugin marketplace add (EBUSY ... rename 'avivsinai-skills-marketplace' -> 'avivsinai-marketplace').
Steps to reproduce
On enterprise Windows with Defender real-time protection ON and no admin rights:
/plugin marketplace add <owner>/<repo>(may need several retries due to EBUSY on the cache rename)/plugin install <plugin>@<marketplace>
Expected
Plugin installs reliably over HTTPS without requiring SSH/port 22 or admin-level AV exclusions.
Actual
Fails intermittently at the rename or git-checkout step. enabledPlugins gets written to settings.json, but the plugin files never land in the cache, so the plugin is "enabled" yet absent. Retrying sometimes succeeds (the lock is transient) but is unreliable; leftover temp_github_* dirs accumulate in ~/.claude/plugins/cache.
Root cause
The temp-clone-then-rename pattern and git's lock-file renames are POSIX-friendly but Windows-hostile: POSIX allows rename over/with open handles; Windows returns EBUSY. Real-time AV scanning of freshly-written files makes the race fire frequently.
Suggested fixes
- Prefer HTTPS for GitHub clones (or detect SSH failure and fall back to HTTPS), rather than defaulting to
git@github.com:. - Retry filesystem rename / git checkout on Windows
EBUSYwith short backoff — transient AV locks clear within a second or two. - Copy-into-place instead of atomic rename on Windows, or write directly to the final path. A file-by-file copy has no whole-directory rename to fail.
- Clean up
temp_github_*dirs on failure so retries start clean. - On a failed install, roll back the
enabledPluginsentry (or mark it pending) so state doesn't claim a plugin is enabled when its files are absent.
Workarounds that worked (for other affected users)
- Global git rewrite to dodge the port-22 block:
````
git config --global --add url."https://github.com/".insteadOf "git@github.com:"
git config --global --add url."https://github.com/".insteadOf "ssh://git@github.com/"
- Manual install: clone the plugin repo over HTTPS, check out the pinned SHA, then robocopy the working tree into
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>(copy avoids the failing rename). Restart Claude Code to load it. - Running Claude Code under WSL2 avoids the Windows rename semantics and AV race entirely.
- A Defender exclusion on
~/.claude/pluginsfixes it at the source, but requires admin/IT.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗