Windows MSIX: stealth auto-update leaves app unlaunchable with 0x80070020 until reboot (non-PTY children keep the AppX container and Helium User.dat hive mounted)
Summary
A stealth (idle-timeout) auto-update of the MSIX-packaged Windows desktop app can leave the app permanently unlaunchable until a reboot. Every launch fails with 0x80070020 (ERROR_SHARING_VIOLATION).
Root cause: beforeQuitForUpdate only kills PTY process trees. Non-PTY child processes spawned by claude.exe (node, cmd, powershell, ssh, MCP servers) inherit the package identity and stay inside the Desktop AppX container. As long as one survives, the container is never destroyed, so the app-silo registry hive …\SystemAppData\Helium\User.dat stays mounted at \REGISTRY\WC\Silo<guid>user_sid. The newly registered package version then creates a new container and calls RegLoadKey on the same User.dat file, which fails with a sharing violation.
Environment
- Windows 11 Pro 10.0.26200
- Claude desktop app, MSIX (
Claude_pzs8sxrjxfjjc), Developer signature kind - Version at failure: upgrading
1.44121.4.0->1.46388.2.0 - Packaged service present:
CoworkVMService(app\resources\cowork-svc.exe, LocalSystem, auto-start)
Symptoms
- App does not start. Windows shows a "file is in use by another program" style error.
- No
Claude.exeprocesses are running. Microsoft-Windows-AppModel-Runtime/Admin:- Event 215:
0x80070020: cannot create Desktop AppX container for package Claude_1.46388.2.0_x64__pzs8sxrjxfjjc - Event 208:
0x80070020: cannot create process for package Claude_1.46388.2.0_x64__pzs8sxrjxfjjc [LaunchProcess] - Process Monitor shows
RegLoadKey/CreateFileon
C:\Users\<user>\AppData\Local\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat
returning SHARING VIOLATION, issued by svchost.exe (Appinfo service) — the victim, not the holder.
- The hive remains listed in
HKLM\SYSTEM\CurrentControlSet\Control\hivelistunder\REGISTRY\WC\Silo<guid>user_sid.
Timeline from the logs (single reproduction, times local)
16:33:33boot.1.44121.4.0running:cowork-svc.exein container A,claude.exe(Electron main) in container B.16:35:32AppXDeploymentServer event 400:1.46388.2.0staged successfully. Event 658: "Marking package for deferred registration because 1.44121.4.0 is still running". Event 638 lists running appClaude_pzs8sxrjxfjjc!Claude.18:40:37app main.log:
````
[stealth-update] Triggering stealth update after idle timeout
[stealth-relaunch] Saved z-order anchor / navigation history
[CCD] Killing 4 PTY process tree(s) on quit
18:40:38main.log:beforeQuitForUpdate handler fired, going down for update.
AppXDeploymentServer event 603: RegisterByPackageFamilyName, options ForceApplicationShutdownOption.
18:41:09AppModel-Runtime event 217 removes container A (the packaged service's container). Container B — theclaude.execontainer of the old version — is never removed. First 215/2080x80070020fires in the same second. The stealth relaunch never completes; main.log has no further entries until after the reboot.18:41–19:1471 further 215/208 events across manual launches and Windows' ownRepairAppRegistrationOptionre-registrations. Each re-registration terminatesCoworkVMServicesuccessfully (events 9648/9650, result0x0) and the launch still fails — the service is not the holder.19:15:28on shutdown, event 217 finally removes container B for1.44121.4.0, 2 h 41 min after the app "quit".19:16:11boot. App launches normally.
Why the obvious diagnostics mislead
handle.exefinds no open handle onUser.dat. A silo-mounted hive is a kernelCmLoadKeyreference on the silo object, not a user-mode file handle, so absence of handles proves nothing.- Stopping
CoworkVMServicedoes not help, and the deployment stack already terminates it during repair registration (result0x0) while launches keep failing. NtUnloadKeyon the silo path returnsC000003B(STATUS_OBJECT_PATH_SYNTAX_BAD) — a path-syntax error, not evidence of a lock.
Confirmation that non-PTY children stay in the container
In a healthy session, the app container holds these descendants of claude.exe, none of which are PTY trees:
node.exe <- cmd.exe <- claude.exe <- claude.exe (main)
node.exe <- cmd.exe <- node.exe <- cmd.exe <- claude.exe <- claude.exe (main)
powershell.exe <- claude.exe <- claude.exe (main)
conhost.exe <- claude.exe <- claude.exe (main)
Any of these outliving beforeQuitForUpdate keeps the Desktop AppX container, and therefore the hive, alive.
Steps to reproduce
- Run the packaged app; let it spawn a long-lived child process that is not part of a PTY tree (an SSH connection, an MCP server, a detached background shell).
- Let the auto-updater stage a newer version while the app is running, so registration is deferred (event 658).
- Leave the app idle until the stealth update fires; it quits and force-registers the new version.
- Launch the app. It fails with
0x80070020and keeps failing until a reboot.
Impact
The app is unusable until reboot. No user-mode remedy exists: repair-registration, stopping the packaged service, and manual hive unloading all fail. The failure window is invisible to the user until they try to launch.
Suggested fixes
- On
beforeQuitForUpdate, terminate the whole package process tree, not just PTY trees — or wait for the Desktop AppX container to be torn down (poll for AppModel-Runtime event 217 / container destruction) before triggering registration. - Before the stealth relaunch, verify that no processes remain in the package container; if any do, defer the update to the next opportunity instead of force-registering.
- Detect
0x80070020on launch and surface an actionable message ("restart Windows to finish the update") instead of a generic file-in-use error.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗