# Fix Claude Desktop on Windows (self-corrupting NeedsRemediation)

> Source: <https://gist.github.com/KehuiPang/0a678ac866ed4b9b883e9a30f4b63372>
> Published: 2026-08-16 05:42:55+00:00

|
<# |
|
.SYNOPSIS |
|
Fixes Claude Desktop on Windows that keeps breaking with |
|
"This app can't open" / silently quits on launch. |
|
|
|
.DESCRIPTION |
|
Newer Claude Desktop MSIX (Store) builds bundle vk_swiftshader.dll. |
|
On machines that enforce Code Integrity, Windows blocks that DLL inside |
|
the MSIX container (event 3033: "did not meet the Microsoft signing level |
|
requirements"). That crashes the GPU process (0x060C201E), which makes |
|
Windows flag the whole package as "Modified, NeedsRemediation", so the |
|
next launch fails with 0x3CFC. Repair/reinstall only fixes it for a few |
|
minutes, then it self-corrupts again. |
|
|
|
This script sidesteps the bug by deploying the NON-MSIX (Squirrel) build |
|
to %LOCALAPPDATA%\AnthropicClaude, which lives OUTSIDE the MSIX container, |
|
so the signing enforcement never applies. Same Claude, no self-corruption. |
|
|
|
What it touches (full transparency): |
|
- Downloads an official Squirrel .nupkg from downloads.claude.ai |
|
- Extracts it to %LOCALAPPDATA%\AnthropicClaude\app-<version>\ |
|
- Creates Desktop + Start Menu shortcuts to Claude.exe |
|
- Registers the claude:// protocol handler (HKCU) so OAuth / Google |
|
sign-in redirects back into the app instead of looping forever on |
|
the sign-in screen |
|
- (optional -Lock) adds "0.0.0.0 downloads.claude.ai" to the hosts file |
|
to stop it auto-updating back into the broken MSIX build (needs admin) |
|
- (optional -RemoveBadMsix) uninstalls the broken MSIX package |
|
- (optional -MigrateData) copies your old MSIX chat/session data over |
|
so your history isn't "wiped" after the switch |
|
|
|
It does NOT delete your Claude data / sign-in (that lives elsewhere). |
|
|
|
.PARAMETER Version |
|
Specific version to install, e.g. 1.22209.3. Omit to auto-detect the |
|
latest from the RELEASES manifest. |
|
|
|
.PARAMETER Lock |
|
Also lock the version via the hosts file so Claude can't auto-update back |
|
into the broken MSIX build. Requires an elevated (Administrator) shell. |
|
|
|
.PARAMETER RemoveBadMsix |
|
Also uninstall the broken MSIX (Store) package. |
|
|
|
.PARAMETER MigrateData |
|
Copy your existing chat/session data from the old MSIX package folder |
|
into the new build so your history carries over (the two builds use |
|
different data directories, so a fresh switch otherwise looks "wiped"). |
|
|
|
.EXAMPLE |
|
.\Fix-ClaudeDesktop.ps1 |
|
Deploy the latest Squirrel build and make shortcuts. |
|
|
|
.EXAMPLE |
|
.\Fix-ClaudeDesktop.ps1 -Lock -RemoveBadMsix |
|
(Run as Admin) Deploy, remove the broken MSIX, and lock the version. |
|
|
|
.EXAMPLE |
|
.\Fix-ClaudeDesktop.ps1 -Version 1.22209.3 |
|
Pin a specific known-good version. |
|
|
|
.NOTES |
|
Author : Cheng (building Wuwei - wuweiai.io) |
|
License : MIT. Use at your own risk, no warranty. |
|
The manual steps are documented in the thread; this just automates them. |
|
#> |
|
|
|
param( |
|
[string]$Version = "", |
|
[switch]$Lock, |
|
[switch]$RemoveBadMsix, |
|
[switch]$MigrateData # copy old MSIX chat/session data into the new build |
|
) |
|
|
|
$ErrorActionPreference = "Stop" |
|
|
|
# TLS 1.2 for older Windows/PowerShell so the download doesn't fail |
|
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} |
|
|
|
$base = "https://downloads.claude.ai/releases/win32/x64" |
|
$root = Join-Path $env:LOCALAPPDATA "AnthropicClaude" |
|
$hostFile = "$env:SystemRoot\System32\drivers\etc\hosts" |
|
|
|
function Info($m){ Write-Host "[*] $m" -ForegroundColor Cyan } |
|
function Good($m){ Write-Host "[OK] $m" -ForegroundColor Green } |
|
function Warn($m){ Write-Host "[!] $m" -ForegroundColor Yellow } |
|
|
|
function Test-Admin { |
|
$id = [Security.Principal.WindowsIdentity]::GetCurrent() |
|
(New-Object Security.Principal.WindowsPrincipal($id)).IsInRole( |
|
[Security.Principal.WindowsBuiltInRole]::Administrator) |
|
} |
|
|
|
Write-Host "" |
|
Write-Host " Fix-ClaudeDesktop - non-MSIX Squirrel deploy" -ForegroundColor White |
|
Write-Host " by Cheng | wuweiai.io" -ForegroundColor DarkGray |
|
Write-Host "" |
|
|
|
# --- 0) If a previous run locked the update host, temporarily unlock so we |
|
# can download. We re-lock at the end if it was locked. |
|
$wasLocked = $false |
|
try { |
|
if ((Get-Content $hostFile -ErrorAction SilentlyContinue) -match 'downloads\.claude\.ai') { |
|
$wasLocked = $true |
|
} |
|
} catch {} |
|
|
|
if ($wasLocked) { |
|
if (Test-Admin) { |
|
Info "Version lock found in hosts; temporarily unlocking to download..." |
|
(Get-Content $hostFile) | |
|
Where-Object { $_ -notmatch 'downloads\.claude\.ai' -and $_ -notmatch 'Claude desktop lock' } | |
|
Set-Content $hostFile -Encoding ASCII |
|
ipconfig /flushdns | Out-Null |
|
Start-Sleep -Seconds 1 |
|
} else { |
|
Warn "hosts has a version lock (0.0.0.0 downloads.claude.ai) and you're NOT admin." |
|
Warn "The download will fail. Re-run in an Administrator PowerShell, or remove that line first." |
|
} |
|
} |
|
|
|
# --- 1) Resolve version |
|
if (-not $Version) { |
|
Info "Detecting latest version from RELEASES..." |
|
$rel = (Invoke-WebRequest -UseBasicParsing "$base/RELEASES").Content |
|
if ($rel -match 'AnthropicClaude-([0-9.]+)-full\.nupkg') { $Version = $Matches[1] } |
|
if (-not $Version) { throw "Could not detect version from RELEASES." } |
|
} |
|
Info "Target version: $Version" |
|
|
|
# --- 2) Download the Squirrel nupkg |
|
$tmp = Join-Path $env:TEMP "AnthropicClaude-$Version-full.nupkg" |
|
$url = "$base/AnthropicClaude-$Version-full.nupkg" |
|
Info "Downloading $url" |
|
Invoke-WebRequest -UseBasicParsing $url -OutFile $tmp |
|
Good ("Downloaded {0} MB" -f [math]::Round((Get-Item $tmp).Length / 1MB)) |
|
|
|
# --- 3) Extract lib/net45 -> app-<version> |
|
$dst = Join-Path $root "app-$Version" |
|
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force } |
|
New-Item -ItemType Directory -Path $dst -Force | Out-Null |
|
|
|
Info "Extracting to $dst" |
|
Add-Type -AssemblyName System.IO.Compression.FileSystem |
|
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmp) |
|
try { |
|
foreach ($e in $zip.Entries) { |
|
if ($e.FullName -notlike "lib/net45/*") { continue } |
|
if ($e.FullName.EndsWith("/")) { continue } |
|
$relPath = $e.FullName.Substring("lib/net45/".Length) |
|
$target = Join-Path $dst $relPath |
|
$dir = Split-Path $target -Parent |
|
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } |
|
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($e, $target, $true) |
|
} |
|
} finally { |
|
$zip.Dispose() |
|
} |
|
|
|
$exe = Join-Path $dst "Claude.exe" |
|
if (-not (Test-Path $exe)) { throw "Claude.exe not found after extract - the package layout may have changed." } |
|
Good "Deployed Claude.exe (non-MSIX)" |
|
|
|
# --- 4) Optionally remove the broken MSIX package |
|
if ($RemoveBadMsix) { |
|
$pkg = Get-AppxPackage *claude* -ErrorAction SilentlyContinue |
|
if ($pkg) { |
|
Info "Removing broken MSIX package: $($pkg.PackageFullName)" |
|
Get-Process | Where-Object { $_.Path -like '*WindowsApps\Claude*' } | |
|
Stop-Process -Force -ErrorAction SilentlyContinue |
|
$pkg | Remove-AppxPackage -ErrorAction SilentlyContinue |
|
Good "MSIX package removed" |
|
} else { |
|
Info "No MSIX Claude package found (nothing to remove)." |
|
} |
|
} |
|
|
|
# --- 5) Shortcuts (Desktop + Start Menu) |
|
$ws = New-Object -ComObject WScript.Shell |
|
foreach ($loc in @( |
|
[Environment]::GetFolderPath('Desktop'), |
|
(Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs') |
|
)) { |
|
$lnk = $ws.CreateShortcut((Join-Path $loc 'Claude.lnk')) |
|
$lnk.TargetPath = $exe |
|
$lnk.WorkingDirectory = $dst |
|
$lnk.IconLocation = "$exe,0" |
|
$lnk.Save() |
|
} |
|
Good "Created Desktop and Start Menu shortcuts" |
|
|
|
# --- 5.5) Register the claude:// protocol handler (HKCU) |
|
# Why: after Google/OAuth sign-in completes in the browser, Windows hands |
|
# the result back via a claude://... callback. A hand-extracted nupkg (or a |
|
# broken MSIX) can leave HKCU\Software\Classes\claude without a |
|
# shell\open\command, so the callback goes nowhere and the app loops on the |
|
# sign-in screen forever. Email + verification code works because it never |
|
# uses the deep-link. This points the protocol at the Claude.exe we deployed. |
|
try { |
|
$cmd = "`"$exe`" `"%1`"" |
|
New-Item -Path 'HKCU:\Software\Classes\claude' -Force | Out-Null |
|
New-ItemProperty -Path 'HKCU:\Software\Classes\claude' -Name '(default)' -Value 'URL:claude' -PropertyType String -Force | Out-Null |
|
New-ItemProperty -Path 'HKCU:\Software\Classes\claude' -Name 'URL Protocol' -Value '' -PropertyType String -Force | Out-Null |
|
New-Item -Path 'HKCU:\Software\Classes\claude\shell\open\command' -Force | Out-Null |
|
New-ItemProperty -Path 'HKCU:\Software\Classes\claude\shell\open\command' -Name '(default)' -Value $cmd -PropertyType String -Force | Out-Null |
|
Good "Registered claude:// protocol handler -> OAuth / Google sign-in will redirect back into the app" |
|
} catch { |
|
Warn "Could not register claude:// handler ($($_.Exception.Message)). Sign-in still works via email + verification code." |
|
} |
|
|
|
# --- 5.6) Verify the claude:// handler points at THIS build (self-check) |
|
# Confirms the registration above actually landed and points at the exe we |
|
# just deployed. If a stale entry points at an old app-<version> path, the |
|
# Google/OAuth callback lands nowhere and you loop on the sign-in screen. |
|
try { |
|
$reg = (Get-ItemProperty 'HKCU:\Software\Classes\claude\shell\open\command' -ErrorAction Stop).'(default)' |
|
if ($reg -like "*$dst*") { |
|
Good "Verified claude:// -> this build. Google / OAuth sign-in will land back in the app." |
|
} else { |
|
Warn "claude:// points at a different path ($reg). Repointing to this build..." |
|
New-ItemProperty -Path 'HKCU:\Software\Classes\claude\shell\open\command' -Name '(default)' -Value "`"$exe`" `"%1`"" -PropertyType String -Force | Out-Null |
|
Good "claude:// repointed to $exe" |
|
} |
|
} catch { |
|
Warn "claude:// handler not found after registration; sign-in may loop. Re-run the script, or sign in with email + verification code." |
|
} |
|
|
|
# --- 5.7) Optionally migrate old chat/session data |
|
# The MSIX (Store) build stores data under its sandboxed package folder, |
|
# while the Squirrel build uses %APPDATA%\Claude. After switching, history |
|
# looks "wiped" only because the new build can't see the old sandboxed data. |
|
# Nothing was deleted; this copies it over. |
|
if ($MigrateData) { |
|
$newData = Join-Path $env:APPDATA 'Claude' |
|
$msixRoot = Join-Path $env:LOCALAPPDATA 'Packages' |
|
$src = $null |
|
if (Test-Path $msixRoot) { |
|
$src = Get-ChildItem $msixRoot -Directory -Filter 'Claude_*' -ErrorAction SilentlyContinue | |
|
ForEach-Object { Join-Path $_.FullName 'LocalCache\Roaming\Claude' } | |
|
Where-Object { Test-Path $_ } | Select-Object -First 1 |
|
} |
|
if ($src) { |
|
Info "Migrating old MSIX data:`n from $src` n to $newData" |
|
if (Test-Path $newData) { |
|
$bak = "$newData.bak-$(Get-Date -Format yyyyMMdd-HHmmss)" |
|
Copy-Item $newData $bak -Recurse -Force -ErrorAction SilentlyContinue |
|
Info "Backed up existing new-build data to $bak" |
|
} |
|
New-Item -ItemType Directory -Path $newData -Force | Out-Null |
|
Copy-Item (Join-Path $src '*') $newData -Recurse -Force -ErrorAction SilentlyContinue |
|
Good "Migrated old chat/session data. Your history should reappear after launch." |
|
} else { |
|
Warn "No old MSIX data folder found under $msixRoot (nothing to migrate)." |
|
} |
|
} |
|
|
|
# --- 6) Lock version (block auto-update) if requested, or restore a lock we |
|
# temporarily removed at step 0. |
|
if ($Lock -or $wasLocked) { |
|
if (Test-Admin) { |
|
try { |
|
$lines = Get-Content $hostFile | |
|
Where-Object { $_ -notmatch 'downloads\.claude\.ai' -and $_ -notmatch 'Claude desktop lock' } |
|
$lines += '# Claude desktop lock version (avoid bad MSIX auto-update)' |
|
$lines += '0.0.0.0 downloads.claude.ai' |
|
Set-Content -Path $hostFile -Value $lines -Encoding ASCII |
|
ipconfig /flushdns | Out-Null |
|
Good "Version locked via hosts. Remove that line to allow updates later." |
|
} catch { |
|
Warn "Lock failed - run PowerShell as Administrator to edit hosts. (Fix still works without lock.)" |
|
} |
|
} else { |
|
Warn "Skipping version lock: not admin. (Fix still works; re-run as admin with -Lock.)" |
|
} |
|
} |
|
|
|
Write-Host "" |
|
Good "Done. Launch Claude from the new Desktop shortcut." |
|
Info "It now runs from: $dst" |
|
Info "That path is outside the MSIX container, so no more NeedsRemediation self-corruption." |
|
Write-Host "" |
|
|
|
Start-Process $exe |
