[BUG] "Windows auto-updater EBUSY: running 245MB SEA locks its dir during swap"
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Windows auto-updater fails with EBUSY ("file used by another process") and corrupts/orphans the install — running 245 MB SEA locks its own directory during dir-swap
Environment
- OS: Windows 11 Pro (10.0.26200)
- Install method: npm global, custom prefix (
C:\claude-npm) - Version: 2.1.175 (also observed on 2.1.173 → 2.1.175)
- Antivirus: Microsoft Defender, Real-Time Protection ON, no exclusion for the install dir
- Node: system Node (
C:\Program Files\nodejs)
Summary
On Windows, the in-session auto-updater intermittently fails and, historically, has corrupted the install. Root cause appears structural: the CLI ships as a ~245 MB single-file executable (Node SEA) that holds a mandatory file lock on its own image while running. The updater stages the new version into a sibling directory .claude-code-<random> and then attempts a directory swap. On Windows a directory containing an open file cannot be renamed or deleted, so the swap fails with EBUSY/EPERM ("being used by another process") whenever a claude.exe instance is alive. The result is a failed update plus a leftover staging directory.
Observed evidence (single machine)
- The platform binary is 244,979,872 bytes at both:
…\@anthropic-ai\claude-code\bin\claude.exe…\@anthropic-ai\claude-code\node_modules\@anthropic-ai\claude-code-win32-x64\claude.exe
These share an inode (hardlink, link count 2) — one large SEA, two names.
- A live
claude.exe(the running session) keeps a handle open on that image for its entire lifetime. - After a failed update, an orphaned staging directory
…\@anthropic-ai\.claude-code-<random>\(≈233 MB, containing a fullclaude-code-win32-x64\claude.exe) is left behind and never garbage-collected. Observed a leftover from the previous day still on disk. - Historically (older updater, ~v before the staging pattern) the same lock caused a partial in-place replace, leaving the install in a broken state (
claude.cmd found but failed --version check), which cascaded into MCP server failures.
Why it "sometimes works"
The swap succeeds only when no other claude.exe holds the directory at the moment of promotion. With an in-session auto-updater — which runs inside the very process whose binary it must replace — that condition is impossible to guarantee, so failure is intermittent and, on Windows, structurally unavoidable.
Secondary factor
Defender Real-Time Protection opens the freshly written 245 MB executable for scanning, adding a transient lock window during promotion. Excluding the install dir reduces (but does not eliminate) intermittent failures.
Expected behavior
Updates complete atomically and never leave the install in a broken or partial state, even while one or more claude.exe instances are running.
Suggested fixes (in order of robustness)
- Versioned install + junction/symlink swap. Install each version under
…\claude-code\versions\<version>\and point acurrentjunction at it. "Promotion" = recreate the junction (atomic, touches no open file). Running processes keep their version; new sessions follow the junction. Mirrors Chrome/VS CodeApplication\<version>\. - Per-file rename-aside instead of per-directory swap. On Windows an open file can be renamed (only delete/overwrite is blocked); a directory containing an open file cannot. Rename the live
claude.exeaside (e.g.claude.exe.old-<pid>), write the new one in place, and GC the renamed file once no process holds it. - Deferred swap on next clean launch. If a live instance blocks promotion, keep the staged version and complete the swap at the next startup when no
claude.exeis running (Chrome's pending-update model). - Staging-dir garbage collection. Always clean orphaned
.claude-code-<random>directories on startup; today they accumulate at ~233 MB each.
Workarounds currently in use
DISABLE_AUTOUPDATER=1, update manually only with zero running instances (npm i -g @anthropic-ai/claude-code).Add-MpPreference -ExclusionPath "<install dir>".- A SessionStart hook that detects a truncated
claude.exe(<100 MB) and GCs orphaned staging dirs.
What Should Happen?
Autoactualice correctamente
Error Messages/Logs
Steps to Reproduce
Claude Model
Sonnet (default)
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.175
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗