claude update on Windows reports success but never replaces the launcher (stays on old version)
Summary
On the Windows native install, claude update reports a successful update to the new version, but every new shell keeps launching the previous version. The download step works (the new versioned binary appears under .local/share/claude/versions/), but the final step - replacing the launcher copy at .local/bin/claude.exe - never happens, and the updater records and reports success regardless.
Environment
- OS: Windows 11 Enterprise (26200)
- Install type: native installer (launcher at
~/.local/bin/claude.exe) - Observed updating from 2.1.200 to 2.1.201
Steps / what happens
- Run
claude update- it prints that it successfully updated to 2.1.201. ~/.claude/.last-update-result.jsonrecords{"outcome":"success","status":"success","version_from":"2.1.200","version_to":"2.1.201","error_code":null}..local/share/claude/versions/contains the freshly downloaded 2.1.201 build.- But
.local/bin/claude.exeis still the 2.1.200 build, so every new shell runs 2.1.200. Re-runningclaude updatenever makes progress.
Evidence
- Only one
claude.exeon PATH (~/.local/bin/claude.exe) - this is not a PATH collision. - The launcher is a full (~241 MB) copy of the exe, not a symlink.
- SHA-256 of the launcher equalled the SHA-256 of
versions/2.1.200exactly, whileversions/2.1.201had a different hash. After manually copyingversions/2.1.201over the launcher,claude --versioncorrectly reported 2.1.201.
Likely root cause
The updater is itself running from .local/bin/claude.exe, so Windows locks that file and it cannot be overwritten in place. The rename-then-copy pattern (rename the running exe aside, then copy the new build in - which Windows does permit) does not appear to be used on this path. The failure is also swallowed: the update reports success even though the swap did not occur.
Impact
Users believe they are up to date when they are not, with no signal unless they check claude --version in a fresh shell.
Suggested fixes
- Use rename-then-copy so a running launcher can be replaced.
- If the swap fails, report failure honestly instead of writing outcome "success".
Workaround
Manually copy the newest build over the launcher (with Claude closed, or via the rename trick if it is running):
Move-Item "$env:USERPROFILE/.local/bin/claude.exe" "$env:USERPROFILE/.local/bin/claude.exe.old" -Force
Copy-Item "$env:USERPROFILE/.local/share/claude/versions/<new-version>" "$env:USERPROFILE/.local/bin/claude.exe" -Force
3 Comments
Confirming on Windows 10 (win32-x64), native install, Git Bash via Warp —
same behavior updating 2.1.200 → 2.1.201: update reports success,
claudeshows "Last update attempt: success → 2.1.201", versions/2.1.201 isdoctor
on disk, but the launcher stays 2.1.200 in every new shell.
One additional data point:
claude install 2.1.201fails the same silentway — it prints "✓ Claude Code successfully installed! Version: 2.1.201",
but
claude --versionimmediately after still returns 2.1.200. Consistentwith the self-lock root cause, since the install command is also executed by
the running launcher exe.
The manual copy workaround also works fine from Git Bash (no running claude
process at the time):
cp ~/.local/share/claude/versions/2.1.201 ~/.local/bin/claude.exe
claude --version # → 2.1.201 (Claude Code)
+1 for rename-then-copy and for reporting the failed swap honestly instead
of writing outcome "success".
Still reproducible on 2.1.229 → 2.1.231 (Windows 11 Home 26200, native install).
claude updatewas run from inside a live session (soclaude.exewas the running process). It printed "Successfully updated from 2.1.229 to version 2.1.231",versions/2.1.231landed on disk with a distinct SHA-256, but~/.local/bin/claude.exestill hashed identical toversions/2.1.229— every restart relaunched 2.1.229.One extra data point for the suggested fix: the rename-then-copy swap works even while the old exe is still running —
Move-Itemon the locked launcher succeeds (rename is permitted on a running exe), then copyingversions/2.1.231into.local/bin/claude.execompletes normally and a fresh shell reports 2.1.231. So the updater could self-heal this path today without requiring the session to close first.+1 on reporting the failed swap as a failure instead of success — the false "Successfully updated" is what makes this costly to diagnose.
Reproduced on 2.1.229 → 2.1.231 (Windows 10 Pro 19045, native install). Adding debug-level instrumentation and some constraints that narrow the mechanism.
Setup facts
~/.local/bin/claude.exeis a full ~307 MB regular file, not a symlink (same as OP); only claude on PATH.mklinksucceeds from a non-elevated shell.versions/2.1.229when this repro started. Every failing run below is executed by 2.1.229's own updater; the last succeeding run was executed by 2.1.228's.Test A — all sessions closed. Exited every session, ran
claude updatefrom plain PowerShell. It printed "Successfully updated from 2.1.229 to version 2.1.231";versions/2.1.231appeared; the launcher was untouched (mtime unchanged, fresh shells report 2.1.229). One precision on locks, since the OP raises them: a fully lock-free run is impossible in principle — the updater itself executes from the launcher image, so overwrite-in-place is always blocked duringclaude update. But that lock permits rename (renaming the running launcher aside and copying the new binary in works even with live sessions — that's the manual workaround, confirmed), and the .228→.229 hop succeeded under the identical self-lock a day earlier. So the lock is real but cannot alone explain the failure, and closing sessions changes nothing.Test B — clean state, debug logging. Deleted
versions/2.1.231, re-ran withclaude --debug-file <path> update. The tail:15 ms between installing to
versions/and declaring success, with zero launcher-related lines at this verbosity — no rename, no copy, no symlink operation, no error. (The trailing line is the routine managed-settings/MDM probe —C:\Program Files\ClaudeCodedoesn't exist on this machine and the same line appears at process start. Included for completeness, and as contrast: the settings loader logs its "broken symlink or missing file" condition explicitly, while the launcher step logs nothing.)<details><summary>Complete debug log (username redacted)</summary>
</details>
Test C — no deferred application either. With
versions/2.1.231staged, three fresh launcher launches over the following ~20 minutes never refreshed it. A freshly launched session's startup auto-update even wrote{"outcome":"success","status":"success","version_from":"2.1.229","version_to":"2.1.231","error_code":null}to~/.claude/.last-update-result.jsonseconds after launch — without re-downloading (staged file mtime unchanged) and without touching the launcher. So there is no apply-on-next-launch path that eventually reconciles this, and the success bookkeeping is written regardless.Reading. Consistent with the OP: the launcher-replace step doesn't appear to execute at all — success is declared once the versioned binary lands in
versions/, and whatever should refreshbin/claude.exeis skipped silently, with no attempt and no error at debug verbosity. Given the OP hit this on 2.1.200→2.1.201 in early July while this machine updated cleanly through 2.1.229 on Aug 12, the trigger looks conditional rather than a clean one-shot regression — though on this machine it presents exactly as "the updater shipped in 2.1.229 stopped doing it."Workaround (as in the thread): copy
versions/<new>over~/.local/bin/claude.exe; if any claude process is alive, rename the launcher aside first (Windows permits renaming a running image), then copy. SHA-256 of the copy matched the versioned binary afterward (worth checking given #86295).