claude update on Windows reports success but never replaces the launcher (stays on old version)

Status Open
Reported on v2.1.200
Maintainer reply None cached
Activity 3 comments · opened Jul 6, 2026

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

  1. Run claude update - it prints that it successfully updated to 2.1.201.
  2. ~/.claude/.last-update-result.json records {"outcome":"success","status":"success","version_from":"2.1.200","version_to":"2.1.201","error_code":null}.
  3. .local/share/claude/versions/ contains the freshly downloaded 2.1.201 build.
  4. But .local/bin/claude.exe is still the 2.1.200 build, so every new shell runs 2.1.200. Re-running claude update never makes progress.

Evidence

  • Only one claude.exe on 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.200 exactly, while versions/2.1.201 had a different hash. After manually copying versions/2.1.201 over the launcher, claude --version correctly 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

  1. Use rename-then-copy so a running launcher can be replaced.
  2. 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

View original on GitHub ↗

3 Comments

akte1000 · 1 month ago

Confirming on Windows 10 (win32-x64), native install, Git Bash via Warp —
same behavior updating 2.1.200 → 2.1.201: update reports success, claude
doctor
shows "Last update attempt: success → 2.1.201", versions/2.1.201 is
on disk, but the launcher stays 2.1.200 in every new shell.

One additional data point: claude install 2.1.201 fails the same silent
way
— it prints "✓ Claude Code successfully installed! Version: 2.1.201",
but claude --version immediately after still returns 2.1.200. Consistent
with 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".

stevenpelletier90 · 17 days ago

Still reproducible on 2.1.229 → 2.1.231 (Windows 11 Home 26200, native install).

claude update was run from inside a live session (so claude.exe was the running process). It printed "Successfully updated from 2.1.229 to version 2.1.231", versions/2.1.231 landed on disk with a distinct SHA-256, but ~/.local/bin/claude.exe still hashed identical to versions/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 runningMove-Item on the locked launcher succeeds (rename is permitted on a running exe), then copying versions/2.1.231 into .local/bin/claude.exe completes 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.

winterdeluge · 17 days ago

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.exe is a full ~307 MB regular file, not a symlink (same as OP); only claude on PATH.
  • Machine is symlink-capable: Developer Mode enabled, unprivileged mklink succeeds from a non-elevated shell.
  • Launcher refresh worked as recently as the previous hop: this machine took 2.1.227 → 2.1.228 → 2.1.229 across Aug 11–12, and the launcher was SHA-256-identical to versions/2.1.229 when 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 update from plain PowerShell. It printed "Successfully updated from 2.1.229 to version 2.1.231"; versions/2.1.231 appeared; 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 during claude 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 with claude --debug-file <path> update. The tail:

2026-08-13T13:44:50.194Z [DEBUG] Checking for native installer update to version 2.1.231
2026-08-13T13:44:50.195Z [DEBUG] Downloading native installer version 2.1.231
2026-08-13T13:45:02.192Z [DEBUG] Atomically installed binary to C:\Users\<user>\.local\share\claude\versions\2.1.231
2026-08-13T13:45:02.207Z [DEBUG] Successfully updated to version 2.1.231
2026-08-13T13:45:02.209Z [DEBUG] Broken symlink or missing file encountered for settings.json at path: C:\Program Files\ClaudeCode\managed-settings.json

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\ClaudeCode doesn'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>

2026-08-13T13:44:49.178Z [DEBUG] MDM settings load completed in 12ms
2026-08-13T13:44:49.201Z [DEBUG] Broken symlink or missing file encountered for settings.json at path: C:\Program Files\ClaudeCode\managed-settings.json
2026-08-13T13:44:49.202Z [DEBUG] CA certs: Config fallback - globalEnv keys: , settingsEnv keys: none
2026-08-13T13:44:49.209Z [DEBUG] [init] configureGlobalMTLS starting
2026-08-13T13:44:49.209Z [DEBUG] [init] configureGlobalMTLS complete
2026-08-13T13:44:49.209Z [DEBUG] [init] configureGlobalAgents starting
2026-08-13T13:44:49.209Z [DEBUG] CA certs: stores=bundled,system, extraCertsPath=undefined
2026-08-13T13:44:49.210Z [DEBUG] CA certs: Loaded 121 bundled root certificates
2026-08-13T13:44:49.226Z [DEBUG] CA certs: Dropped 31 expired certificate(s) from system store
2026-08-13T13:44:49.226Z [DEBUG] CA certs: Loaded 181 system CA certificates
2026-08-13T13:44:49.226Z [DEBUG] mTLS: Creating HTTPS agent with custom certificates
2026-08-13T13:44:49.226Z [DEBUG] [init] configureGlobalAgents complete
2026-08-13T13:44:49.227Z [DEBUG] Using bash path: "C:\Program Files\Git\bin\bash.exe"
2026-08-13T13:44:49.229Z [DEBUG] Error log sink initialized
2026-08-13T13:44:49.243Z [DEBUG] update: Starting update check
2026-08-13T13:44:49.243Z [DEBUG] update: Running diagnostic
2026-08-13T13:44:49.328Z [DEBUG] Git remote URL: null
2026-08-13T13:44:49.328Z [DEBUG] No git remote URL found
2026-08-13T13:44:49.666Z [DEBUG] update: Installation type: native
2026-08-13T13:44:49.666Z [DEBUG] update: Config install method: native
2026-08-13T13:44:49.666Z [DEBUG] update: Detected native installation, using native updater
2026-08-13T13:44:50.035Z [DEBUG] Preserving file permissions: 100666
2026-08-13T13:44:50.036Z [DEBUG] Writing to temp file: C:\Users\<user>\.claude.json.tmp.85920.bf8634d670a8
2026-08-13T13:44:50.037Z [DEBUG] Applied original permissions to temp file
2026-08-13T13:44:50.039Z [DEBUG] Temp file written successfully, size: 113281 bytes
2026-08-13T13:44:50.039Z [DEBUG] Renaming C:\Users\<user>\.claude.json.tmp.85920.bf8634d670a8 to C:\Users\<user>\.claude.json
2026-08-13T13:44:50.040Z [DEBUG] File C:\Users\<user>\.claude.json written atomically
2026-08-13T13:44:50.194Z [DEBUG] Checking for native installer update to version 2.1.231
2026-08-13T13:44:50.195Z [DEBUG] Downloading native installer version 2.1.231
2026-08-13T13:45:02.192Z [DEBUG] Atomically installed binary to C:\Users\<user>\.local\share\claude\versions\2.1.231
2026-08-13T13:45:02.207Z [DEBUG] Successfully updated to version 2.1.231
2026-08-13T13:45:02.209Z [DEBUG] Broken symlink or missing file encountered for settings.json at path: C:\Program Files\ClaudeCode\managed-settings.json

</details>

Test C — no deferred application either. With versions/2.1.231 staged, 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.json seconds 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 refresh bin/claude.exe is 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).