Windows desktop: stealth update leaves orphaned processes holding old AppX container; new version unlaunchable (0x80070020) until reboot

Status Open
Maintainer reply None cached
Activity 20 comments · opened Aug 26, 2026

## Summary

The Windows desktop app's stealth auto-update leaves orphaned child processes that keep the OLD version's Desktop AppX container alive. Every launch of the NEW version then fails with 0x80070020 ("error encountered converting the job") until the machine is rebooted. The app appears simply dead to the user: clicking the icon does nothing.

This machine has hit the "update leaves Claude unlaunchable until reboot" state repeatedly across multiple update cycles (an earlier episode instead showed a "Another program is currently using this file" installer dialog while popped-out session processes were still running).

Environment

  • Windows 11 Home 10.0.26200
  • Claude desktop app (MSIX, package family Claude_pzs8sxrjxfjjc) updating 1.34493.1.0 -> 1.37937.0.0
  • Multiple Claude Code sessions / popout panes typically open at update time

Timeline (from Windows event logs, 2026-08-25, all times local)

  1. 13:37 - AppXDeploymentServer: Add operation for 1.37937 with DeferRegistrationWhenPackagesAreInUse; event 658 "Marking package for deferred registration because Claude_1.34493.1.0 is still running". Normal.
  2. 19:57:18 - RegisterByPackageFamilyName with ForceApplicationShutdownOption. Old version's container destroyed, registration finished successfully at 19:57:51 (event 400).
  3. 19:57:51 onward - every relaunch of the new version logs (AppModel-Runtime/Admin):
  • event 211 "Added process <pid> to Desktop AppX container ..." (main process starts)
  • then repeated event 215 0x80070020: Cannot create the Desktop AppX container for package Claude_1.37937.0.0 because an error was encountered converting the job.
  • then event 208 0x80070020: Cannot create the process ... error encountered while configuring runtime. [LaunchProcess]

The app dies silently. This repeats identically at 20:01:31, 20:01:32, 20:01:51, 20:02:11, 20:02:38 (including two automatic re-register/repair passes, both of which "finished successfully" per event 400 yet launches kept failing).

  1. At this point no process named claude.exe existed (verified by an external kill script that logged taskkill /IM claude.exe -> "process not found"). Something else - an orphaned child (conhost/node/shell) without package identity - was evidently still a member of the old container's job: at 20:03:02, during shutdown, the system logged event 217 destroying a Desktop AppX container for the old package 1.34493.1.0 that had been alive the whole time.
  2. Reboot at 20:03:22; first launch at 20:03:47 succeeded instantly.

Analysis

  • beforeQuitForUpdate / stealth update takes down the main app, but child processes spawned by Claude Code sessions (shells, MCP servers, console hosts) can outlive it. They hold membership in the old version's AppX container job.
  • The new version's container creation then fails at job conversion with 0x80070020 until that job is gone. Since these survivors have no package identity and are not named claude.exe, neither Task Manager's "Claude" group nor tasklist /apps surfaces them; a reboot is the only user-discoverable cure.
  • Suggested fix: before (or after) quitForUpdate, enumerate and terminate all processes in the app's own container/job (the app owns the job handle, so it can do this reliably), or make the post-update launcher detect 0x80070020 and clean up the stale container instead of dying silently.

Repro conditions

Not 100% deterministic, but has occurred repeatedly on this machine: several Claude Code sessions with popout panes + background child processes open, stealth update fires after idle timeout, swap succeeds, relaunch fails until reboot.

View original on GitHub ↗

20 Comments

J3TH4CK · 1 month ago

Corroborating forensics from a second machine — same package family, same 1.34493.1.0 → 1.37937.0.0 transition — plus two findings that may help whoever fixes this:

Environment: Windows 11 Pro 10.0.26200, Claude_pzs8sxrjxfjjc, multiple Claude Code sessions open at update time.

Timeline (local time, from event logs):

  • 2026-08-25 16:50:49 — SCM event 7040: service Claude (CoworkVMService) start type changed auto→disabled; 7045: service (re)installed from the Claude_1.37937.0.0 path.
  • 16:50:50 — AppModel-Runtime events 208/215: 0x80070020: Cannot create the Desktop AppX container for package Claude_1.37937.0.0 … an error was encountered converting the job. App icon dead from here on ("Another program is currently using this file" dialog).
  • 2026-08-26 00:29:35, 00:32:30, 00:33:03 — three more launch attempts; each repeats the same triple: SCM 7040 (auto→disabled) + 7045 (service reinstall) + AppModel 208/215 (0x80070020). The launcher reinstalls the packaged service on every attempt without stopping the running instance first.
  • 00:33:03 — AppXDeploymentServer event 400: Register of 1.37937.0.0 finished successfully — yet the launch attempts at that very second still failed with 208/215 (successful re-registration does NOT clear the pinned container).
  • 00:40:13 — System event 1074: user-initiated restart; boot completes 00:40:54.
  • 00:41:54 — app launches normally, only after the reboot. Consistent with the other reports: reboot is what releases the pinned container job.
  • 00:42:36 — the next update (1.37937.1.0) is already staged with DeferRegistrationWhenPackagesAreInUse (event 658: "deferred registration because 1.37937.0.0 is still running") — the same trap is armed for the next quit/relaunch.

(Correction: the first version of this comment claimed recovery happened without a reboot. That was wrong — the boot record shows a user-initiated restart at 00:40, which is what cleared the lock.)

Finding 1 — the packaged service is stoppable by any authenticated user; a mitigation path exists today. sc sdshow CoworkVMService returns:

D:(A;;CCLCSWRPWPDTLOCRRC;;;AU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-80-…)

The first ACE grants Authenticated Users RP (start) and WP (stop); the DACL only reserves delete/reconfigure for the service SID. So the updater — or the app itself at quit time, unelevated — could stop CoworkVMService (and reap any surviving processes of the outgoing package version, e.g. chrome-native-host.exe or elevated Claude Code children — cf. #84792, #73107, #53247) before completing deferred registration and relaunching. Reports that "admin can't touch the service" concern delete/config, not stop. Whether a service stop alone releases the pinned container job (vs. also needing the orphan reap) is untested on this machine — #80286 suggests stop alone may not suffice once the silo is wedged.

Finding 2 — the manifest declares the service with a named-pipe trigger, windows.service + localSystemServices/packagedServices capabilities and <desktop6:DataItem Value="\pipe\cowork-vm-service" />, so the service can be demand-started after the swap; keeping it alive across the package transition is what pins the old container job and produces ERROR_SHARING_VIOLATION at container-job conversion.

Cross-refs with the same signature: #89687, #84435, #80286, #73694. This has been reported since March/April (#45489, #47877, #36466, #48003) — all four were auto-closed by the dedupe bot and locked while the defect stayed alive; please keep this one open.

J3TH4CK · 1 month ago

Field update — the obvious mitigation does NOT work. Stopping CoworkVMService + killing package processes at failure time was tried automatically 4× during a real occurrence today and the launch kept failing; only a reboot recovered.

Since my earlier comment I had installed an automated mitigation on this machine: a scheduled task triggered by AppModel-Runtime event 215 that (a) kills any process still running from an old WindowsApps\Claude_* directory and (b) stops CoworkVMService (permitted to Authenticated Users by the service DACL, per my earlier comment). Today it met a real occurrence:

  • 13:45 — 1.37937.2.0 staged; event 658 deferred registration (1.37937.0.0 running).
  • 17:12:38 — user quit the app; the .0.0 → .2.0 update executed and registered (events 855, 400).
  • 17:13:13 → 17:21:22 — every relaunch failed with 208/215 0x80070020 "error … converting the job" for Claude_1.37937.2.0.
  • The mitigation fired on each burst (4 runs). Its logs show: zero processes running from ANY WindowsApps\Claude_* directory at failure time, and Stop-Service CoworkVMService succeeded on every run — yet the very next launch failed identically. (An external agent attempting manual recovery in parallel also failed.)
  • 17:24 — reboot; app fine since.

So at failure time, whatever pins the old container job is not a visible package-path process, and it is not (only) the running service — stopping the service demonstrably does not release the wedged job/silo. This kills the simplest in-app fix ("stop the service before registering") as a complete solution, though it may still be necessary.

Post-recovery baseline on this machine: the Cowork VM runs as vmmem + vmwp — Host Compute System processes living outside the package directory, invisible to any package-path-based cleanup, and not children of the app. Whether that compute system survives app quit and is what pins the container silo is the open question — it would be consistent with the HCS shutdown failures reported in #83494. I have instrumented the next occurrence: on event 215 the machine now captures a full inventory (package processes, claude/cowork-svc/chrome-native-host/vmmem/vmwp process list, hcsdiag list under an elevated task, service state) before attempting anything. I will post that forensic snapshot when the bug next fires.

For the fix: whatever quit-time/update-time path Anthropic implements, it apparently must also tear down the Cowork HCS compute system (and verify the container silo is actually gone), not merely stop CoworkVMService.

maguro29dev · 1 month ago

Independent reproduction of the same failure signature, adding one data point this thread doesn't have yet: an external recovery script that killed every process named claude, deleted the Electron singleton lockfiles, and restarted CoworkVMService did not recover the app — only a full reboot did (both occurrences).

Environment

  • Windows 11 Home 10.0.26200 (same build as OP)
  • Package family Claude_pzs8sxrjxfjjc, updating 1.37937.2.0 → 1.37937.3.0
  • Several Claude Code sessions had been open earlier that morning

Timeline (2026-08-27, local time)

  • 09:03 — AppXDeploymentServer event 658: 1.37937.3.0 staged, "Marking package for deferred registration because Claude_1.37937.2.0 is still running"
  • 10:39:29 — main.log: [stealth-update] Triggering stealth update after idle timeout → beforeQuitForUpdate handler fired, going down for update → log ends and never resumes
  • 10:39:31 — the very first relaunch attempt already fails: AppModel-Runtime/Admin event 215 0x80070020: Cannot create the Desktop AppX container for package Claude_1.37937.3.0_x64__pzs8sxrjxfjjc because an error was encountered converting the job. followed by event 208 ([LaunchProcess] fails with the same code)
  • 10:47–10:56 — 10 manual launch attempts. TWinUI event 1621 records every one as successful; AppXDeploymentServer re-runs Register on every attempt and event 400 reports "finished successfully" every time; each attempt produces the same 215/208 0x80070020 pair and no app process ever appears
  • 10:59 — reboot; next launch is clean

What did not recover it (run between the manual launch attempts):

  • Stop-Process -Force on every process named claude (both the WindowsApps path and the bundled claude-code path)
  • deleting %APPDATA%\Claude\lockfile and the LocalCache copy
  • restarting CoworkVMService (elevated)
  • the implicit re-registers triggered by each activation (Register "succeeds", launch still fails with 0x80070020)

No crash dumps (Crashpad reports empty) and no Application-log errors from the app itself — the new version simply never gets a process off the ground. This is consistent with the orphaned-job analysis in the OP and with the helper-process root-cause trace in #89648: whatever survives isn't named claude, so name-based kills don't clear the old container's job.

Prior occurrence on the same machine: 2026-08-21, updating 1.34493.0.0 → 1.34493.1.0 — main.log ends at 15:28:26 with the same beforeQuitForUpdate sequence; 4 manual launches over the following ~3 hours all no-op'd (same repeated-Register pattern in AppXDeploymentServer); recovered by the machine's scheduled 05:00 reboot the next morning.

Happy to pull more log detail from this machine if useful.

J3TH4CK · 1 month ago

Forensic snapshot captured during a live occurrence, as promised — the pinner is identified: an orphaned chrome-native-host.exe from the previous container epoch, living in the package's AppData profile (not the install dir). Sign-out cured it without a reboot. The Cowork-VM hypothesis is dead: hcsdiag list was EMPTY during the failure.

Timeline 2026-08-28 (local, Windows 11 Pro 26200, same machine as my comments above):

  • 11:20–11:25 — 1.40609.0.0 staged; event 658 deferred registration (1.37937.3.0 running).
  • ~11:44 — app exited (updater-driven); 11:44:59–11:45:00 — register .37937.3.0 → .40609.0.0 finished successfully (events 855/400).
  • 11:45:00 and 11:50:30 — relaunches fail with 208/215 0x80070020 "error … converting the job" for 1.40609.0.0.
  • Instrumented task snapshot at BOTH failures:
  • Only process under the install dir: a cowork-svc.exe freshly spawned by the launch attempt itself. It was killed and CoworkVMService stopped successfully — did not cure.
  • hcsdiag list (elevated): empty — no Host Compute System VMs existed, ruling out the Cowork VM as the pinner.
  • chrome-native-host.exe PID 26392, running since 2026-08-26 17:26:41 — two days and two version transitions old — at %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\ChromeNativeHost\chrome-native-host.exe. It lives in the package data profile, outside WindowsApps, so package-dir-based cleanup (mine, and apparently the updater's) never touches it.
  • 11:50:31 — my mitigation escalated to suggesting sign-out. User signed out and back in: app launched normally. No reboot. After relaunch, a fresh chrome-native-host.exe spawns ~6 s after the app starts — consistent with an app-managed helper that is never reaped on quit/update.

Conclusion (matches #84792 and #73107): the app leaves chrome-native-host.exe running after quit; as a leftover member of the old version's AppX container job/silo it blocks the new container's job conversion with ERROR_SHARING_VIOLATION until it dies. Sign-out kills session processes — hence the cure; reboot was never actually necessary.

Suggested fix: the quit/update path must terminate ChromeNativeHost (and any other helpers running from the package data profile) before completing registration, and verify the old container job is actually gone before relaunching.

My local mitigation now also reaps package-profile processes on event 215; I will report whether that makes the second click succeed without sign-out at the next occurrence.

jerds19 · 1 month ago

Recurred 2026-08-28, updating 1.37937.3.0 -> 1.40609.0.0 — with new evidence that the container-keeper is not any obvious process.

Same signature: after the update swap, every launch of the new version failed with event 215/208 0x80070020 ... error encountered converting the job (AppModel-Runtime/Admin, 20:20:46–20:21:13), and at shutdown (20:21:49) Windows logged destroying a Desktop AppX container belonging to the old version 1.37937.3.0 that had been alive throughout. Reboot at 20:22 fixed it; first post-boot launch succeeded instantly.

New data point: this time an external cleanup script swept aggressively between launch attempts and the failure still persisted. The sweep force-killed (a) all claude.exe, (b) every process whose command line references the package/appdata paths (caught a cmd.exe and a chrome-native-host.exe), and (c) verified via GetPackageFullName that zero remaining processes carried Claude package identity. Launches still failed with 0x80070020 afterwards.

So the old container's job is being kept alive by something with a generic name, a generic command line, and either no queryable package identity or protection from user-level OpenProcess — plausibly a RuntimeBroker/dllhost/backgroundTaskHost instance or a leaked kernel job handle. From the user side this is effectively undiagnosable and unkillable; reboot is the only recovery.

This strengthens the case that the fix needs to live in the app/updater: terminate all members of the app's own container job on quitForUpdate (the app owns the job handle), or have the post-update launcher detect 0x80070020 and tear down the stale container rather than dying silently — the user just sees a dead icon.

bugale · 1 month ago

Kernel-level mechanism confirmed + an in-place recovery that needs neither reboot nor sign-out — verified while the pinning process was still alive.

Environment: Windows 11 x64 10.0.26200, Claude_pzs8sxrjxfjjc 1.37937.1.0 → 1.37937.3.0. Update staged 08-27 05:00 (deferred registration, app running), force-registered 05:10; every launch for the next two days died with AppModel-Runtime 215/208 0x80070020, including after two RegisterByPackageFullName … RepairAppRegistrationOption passes that each "finished successfully".

What exactly stays pinned: the package's Helium registry hives, still mounted in the dead container's silo

What "error encountered converting the job" actually trips on (open question in this thread): during the failure, the package's virtual-registry hive files are exclusively locked:

  • %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat
  • %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\UserClasses.dat

Restart Manager names the holders as System (PID 4) and Registry (PID 468) — no user-mode process holds file handles. The hives are still loaded in the kernel registry, mounted under the OLD container epoch's silo. HKLM\SYSTEM\CurrentControlSet\Control\hivelist shows it directly:

\REGISTRY\WC\Silob857dc01-…user_sid       => …\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat
\REGISTRY\WC\Silob857dc01-…user_classes   => …\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\UserClasses.dat
\REGISTRY\WC\Silob857dc01-…software / …com => C:\ProgramData\Packages\Claude_pzs8sxrjxfjjc\<user-sid>\SystemAppData\Helium\Cache\<hash>.dat

Launching the new version = creating a new container = mounting those same hive files → ERROR_SHARING_VIOLATION → the "Another program is currently using this file" box (the dialog shows the current Claude.exe path only because the error surfaces from container setup — the exe itself opens fine with any sharing mode during the failure).

Consistent with the forensic snapshot above, the only surviving pre-update-epoch process on my machine was an orphaned chrome-native-host.exe (plus its cmd.exe pipe wrapper), spawned by Chrome three days before the update from %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\ChromeNativeHost\. Two details that explain why kill-script mitigations reported here didn't work:

  • it lives in the package AppData profile, so anything matching processes by WindowsApps\Claude_* path misses it;
  • it holds no handles on the silo or package binaries (handle64 -a on the silo name finds nothing; only its own CWD) — it pins the silo purely by job/silo membership, so handle-hunting also misses it.

In-place recovery (no reboot, no sign-out), verified live

With SeRestorePrivilege enabled, NtUnloadKey2(…, REG_FORCE_UNLOAD) on the stale Claude entries in hivelist releases the hive files instantly; the next launch built a fresh container and the app started normally. Notably, I did not kill the pinning chrome-native-host.exe — it is still running now, harmlessly, next to the working app. So this recovery works even when the silo member can't be found at all.

Unload results for the five stale mounts:

…user_sid        normal => STATUS_SUCCESS
…software        normal => STATUS_SUCCESS
…user_classes    normal => 0xC0000121 (STATUS_CANNOT_DELETE), force => STATUS_SUCCESS
…com             normal => STATUS_SUCCESS
…com (second silo, _COM15 cache) => 0xC0000121 even with force — didn't matter, launch succeeded anyway

i.e. only the user_sid/user_classes/software/com mounts of the dead silo actually block container creation.

PoC (elevated PowerShell):

$src = @'
using System; using System.Text; using System.Runtime.InteropServices;
public static class HiveUnloader {
  [StructLayout(LayoutKind.Sequential)] public struct OA { public int Length; public IntPtr Root; public IntPtr Name; public uint Attr; public IntPtr Sd; public IntPtr Sqos; }
  [StructLayout(LayoutKind.Sequential)] public struct US { public ushort Len; public ushort MaxLen; public IntPtr Buf; }
  [DllImport("ntdll.dll")] public static extern int NtUnloadKey2(ref OA oa, uint flags);
  [StructLayout(LayoutKind.Sequential)] public struct LUID { public uint Low; public int High; }
  [StructLayout(LayoutKind.Sequential)] public struct TOKPRIV { public uint Count; public LUID Luid; public uint Attr; }
  [DllImport("advapi32.dll")] static extern bool OpenProcessToken(IntPtr p, uint acc, out IntPtr tok);
  [DllImport("advapi32.dll", CharSet=CharSet.Unicode)] static extern bool LookupPrivilegeValue(string s, string n, out LUID l);
  [DllImport("advapi32.dll")] static extern bool AdjustTokenPrivileges(IntPtr t, bool d, ref TOKPRIV tp, uint len, IntPtr pr, IntPtr rl);
  [DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
  public static void EnableRestorePriv() {
    IntPtr tok; OpenProcessToken(GetCurrentProcess(), 0x28, out tok);
    LUID luid; LookupPrivilegeValue(null, "SeRestorePrivilege", out luid);
    var tp = new TOKPRIV { Count = 1, Luid = luid, Attr = 2 };
    AdjustTokenPrivileges(tok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
  }
  public static int Unload(string path, bool force) {
    var b = Encoding.Unicode.GetBytes(path);
    var bp = Marshal.AllocHGlobal(b.Length + 2); Marshal.Copy(b, 0, bp, b.Length);
    var us = new US { Len = (ushort)b.Length, MaxLen = (ushort)(b.Length + 2), Buf = bp };
    var usp = Marshal.AllocHGlobal(Marshal.SizeOf(us)); Marshal.StructureToPtr(us, usp, false);
    var oa = new OA { Length = Marshal.SizeOf(typeof(OA)), Name = usp, Attr = 0x40 };
    return NtUnloadKey2(ref oa, force ? 1u : 0u);
  }
}
'@
Add-Type -TypeDefinition $src
[HiveUnloader]::EnableRestorePriv()
$hl = Get-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\hivelist'
foreach ($mount in $hl.GetValueNames()) {
  if ([string]$hl.GetValue($mount) -match 'Claude') {
    $st = [HiveUnloader]::Unload($mount, $false)
    if ($st -ne 0) { $st = [HiveUnloader]::Unload($mount, $true) }
    '{0} => 0x{1:X8}' -f $mount, $st
  }
}
# then just launch the app normally

One more corroborating detail: stopping CoworkVMService does not release these hives (matches the field reports above) — and every launch attempt and every Repair pass reinstalls and restarts it (paired SCM 7045/7040 events each time, new PID each time), which is part of why "Repair" never helps.

Fix-wise this supports what's already been suggested: before registering the staged package, the updater should tear down all members of the package's container silo(s) — explicitly including Chrome-owned native-messaging hosts under LocalCache\Roaming\Claude\ChromeNativeHost — or, failing that, force-unload the package's stale \REGISTRY\WC\Silo* mounts exactly as above instead of leaving the user to reboot.

J3TH4CK · 1 month ago

Resolved on this machine today, and the fix has a step this thread hasn't mentioned yet: the app's MSIX StartupTask has to be disabled before you sign out. That single detail may explain why sign-out works for some people here and not others.

Environment: Windows 11 Pro 10.0.26200, Claude_pzs8sxrjxfjjc, 1.40609.0.0 → 1.40609.1.0.

The recipe that worked

  1. Settings → Apps → Startup → turn Claude off.
  2. Sign out and back in (no reboot).
  3. Do not launch the app. Verify with Get-AppxPackage -Name Claude.
  4. Re-enable Startup, launch.

Before: 1.40609.0.0, silos 5d197b85-…-2758801b3aa2 and 7b931dd7-…-8459c8546278.
After: 1.40609.1.0, silos caa4db34-…-cd17292fd206 and 24e5121f-…-a008715a133a. Both old silos gone.

Why step 1 matters

ClaudeStartup ships as StartupTaskState = 2 (Enabled). If you sign out with it enabled, the app relaunches during logon, a fresh silo is created, and the pending registration is deferred again. From the outside this looks exactly like "signing out didn't help" — which is what I'd have reported had I not checked the registry key first.

Two measurements that narrow the root cause

1. Add-AppxPackage -Register fails with zero package processes alive. With the app closed and CoworkVMService stopped, I verified no process had an image path under WindowsApps\Claude_* or Packages\Claude_pzs8sxrjxfjjc\*, then ran:

Add-AppxPackage -Path "...\Claude_1.40609.1.0_x64__pzs8sxrjxfjjc\AppxManifest.xml" -Register -DisableDevelopmentMode
0x80073D02: The package could not be installed because resources it modifies are currently in use.
Unable to install because the following apps need to be closed Claude_1.40609.0.0_x64__pzs8sxrjxfjjc.

Windows asked me to close an app that had no processes. An exclusive-open test on the package hives right after reported both as locked: SystemAppData\Helium\User.dat and UserClasses.dat. This is direct evidence that the blocker is the mounted hive, not a surviving process — which is also why every process-killing mitigation in this thread, including three of my own, never worked.

2. NtUnloadKey2 returns STATUS_ACCESS_DENIED while the silo is live. Following @bugale's PoC above, elevated, with SeRestorePrivilege enabled and REG_FORCE_UNLOAD, all five Claude mounts returned 0xC0000022:

\REGISTRY\WC\Silo5d197b85-…software      => 0xC0000022 (forced)
\REGISTRY\WC\Silo5d197b85-…user_sid      => 0xC0000022 (forced)
\REGISTRY\WC\Silo5d197b85-…user_classes  => 0xC0000022 (forced)
\REGISTRY\WC\Silo7b931dd7-…com           => 0xC0000022 (forced)
\REGISTRY\WC\Silo5d197b85-…com           => 0xC0000022 (forced)
remaining: 5

The hives stayed locked. I believe the difference from @bugale's STATUS_SUCCESS is silo state: their unload ran against a dead silo after a failed launch, mine against a live one that had never been torn down (same silo id since at least 15:18 that day, across several app closes). If that holds, the in-place recovery works only after the container is already wedged, and cannot be used preemptively.

Deferred registration is never retried inside a live session

The update request originates from the app itself (event 854, fetching from downloads.claude.ai). Windows refuses because the app is running (event 638, Running apps: {Claude_pzs8sxrjxfjjc!Claude}) and marks it deferred (event 658). With the app closed from 16:27 to 16:39, AppXDeploymentServer/Operational logged no deployment events at all for the package. Nothing retries. The request only exists while the app runs; the registration can only complete while it doesn't.

Suggested fix

Before registering a staged package, tear down the outgoing container's silo, and don't rely on process enumeration to decide it is safe — the blocking resource is the mounted hive set, which outlives every process. Failing that, the updater could retry the deferred registration on app exit instead of leaving it for a logon that may never happen without one.

Also worth considering: shipping ClaudeStartup enabled by default actively defeats the sign-out workaround that this thread currently recommends.

Happy to run further diagnostics on request; the machine is instrumented for this now.

cplagz · 1 month ago

Third-party confirmation of @bugale's hive analysis, from a ProcMon capture rather than hivelist — plus one detail on the dialog's wording that I think is worth treating as a bug in its own right.

Environment: Windows 11 x64, Claude_pzs8sxrjxfjjc. Hit across five package versions over ~1 week: 1.37937.1.0, 1.37937.3.0, 1.40609.0.0, 1.40609.1.0, 1.44121.2.0.

ProcMon capture at the moment of failure

Filtered to SHARING VIOLATION / ACCESS DENIED, taken while clicking the Start-menu shortcut:

svchost.exe   2228   RegLoadKey   \Registry\WC\Silo578319a-4ce3-34df-5f42-4e998406cb5duser_sid            SHARING VIOLATION
svchost.exe   2228   CreateFile   ...\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat         SHARING VIOLATION
svchost.exe   16044  CreateFile   ...\Packages\Claude_pzs8sxrjxfjjc\Settings\roaming.lock                 SHARING VIOLATION
Explorer.EXE  3800   CreateFile   ...\Claude_1.44121.2.0_x64__pzs8sxrjxfjjc\app\claude.exe                ACCESS DENIED

This is @bugale's conclusion observed live at the syscall: container setup fails on RegLoadKey of the silo hive, not on anything to do with claude.exe. Adds Settings\roaming.lock to the set of contended files alongside User.dat/UserClasses.dat.

The Explorer.EXE -> claude.exe -> ACCESS DENIED rows are normal for any MSIX app (unprivileged processes have no read access to WindowsApps content) and are unrelated — worth stating explicitly, since that row is what a casual capture will show first and it is a dead end.

Everything ruled out, with the command that ruled it out

Posting the full negative space because this thread's history shows people re-walking it. Each of these was checked on my machine during a live occurrence:

| Hypothesis | Check | Result |
|---|---|---|
| A process holds claude.exe | handle64 "WindowsApps\Claude" | no handles |
| Something maps the package dir | Get-Process → .Modules sweep | nothing |
| Out-of-package children in the container | GetPackageFamilyName() on every PID | only cowork-svc |
| A process holds the hive | handle64 -a "User.dat" | no matching handles |
| Another user session | Get-AppxPackage -AllUsers | single SID |
| Partially staged package | Status, IsPartiallyStaged | Ok, False |
| Broken ACLs on the package dir | Get-Acl | stock TrustedInstaller / SYSTEM |
| Deployment stack failure | Get-AppxLog -All | all operations succeed |

On the last one: Get-AppxLog showed RegisterByPackageFullName completing in ~313 ms, event 400 "finished successfully", and Windows terminating CoworkVMService itself (9648/9650, 0x0) — while launches kept failing. Matches the repeated-Register pattern @maguro29dev and @jerds19 both report.

The dialog is independently wrong, and it is expensive

The shell dialog names C:\Program Files\WindowsApps\Claude_<ver>_x64__... — a file that is not locked and opens fine during the failure. Every diagnostic instinct it triggers is a dead end: process hunting, handle.exe, file locks, AV exclusions, ACLs. I burned about a week on that path before capturing the syscall trace; this thread contains several people who burned considerably longer, including four months chasing filter drivers.

Even if the container teardown fix is involved, a launcher that detected 0x80070020 and said "Claude needs you to sign out and back in (or unload its stale registry hives) to recover from an interrupted update" would convert a multi-day investigation into a 30-second fix. That seems cheap relative to the root-cause work, and independently valuable given how many duplicate reports this has generated.

Note on @bugale's NtUnloadKey2 recovery

Working here. I've wrapped it in a small PowerShell function that reads hivelist, reports stale Claude mounts as a health check, and force-unloads them on request — useful because "hives mounted with no package process running" is a much better wedged-state signal than anything process-based. Happy to share if it's of use to anyone; it is @bugale's PoC with a detection front-end and the ClaudeStartup caveat from @J3TH4CK's recipe wired into the failure path.

<img width="1070" height="423" alt="Image" src="https://github.com/user-attachments/assets/aa6a05a4-c9c7-49b7-b310-920dde792f96" />

cplagz · 1 month ago

Second machine confirming @bugale's NtUnloadKey2 recovery — worked, no sign-out, no reboot — plus two details that may be useful.

Environment: Windows 11 x64, Claude_pzs8sxrjxfjjc, stealth update 1.44121.2.0 → 1.44121.4.0. Classic presentation: dialog naming ...\Claude_1.44121.4.0_..., no package processes alive.

Result

Claude 1.44121.4.0  (Ok)
  package processes : 0
  CoworkVMService   : Running
  mounted Claude hives : 8
    \REGISTRY\WC\Silod578319a-...software      normal => STATUS_SUCCESS
    \REGISTRY\WC\Silod578319a-...user_sid      forced => STATUS_SUCCESS
    \REGISTRY\WC\Silod578319a-...user_classes  forced => STATUS_SUCCESS
    \REGISTRY\WC\Silod578319a-...com           normal => STATUS_SUCCESS
    \REGISTRY\WC\Silo64091e35-...software      normal => STATUS_SUCCESS
    \REGISTRY\WC\Silo64091e35-...com           normal => STATUS_SUCCESS
    \REGISTRY\WC\Silo994e425f-...com           forced => 0xC0000121
    \REGISTRY\WC\Silocebb8ace-...com           forced => 0xC0000121

App launched immediately afterwards. Matches @bugale's finding precisely, including that leftover com mounts returning 0xC0000121 (STATUS_CANNOT_DELETE) don't block anything — only the dead silo's user_sid / user_classes / software / com set matters.

Detail 1 — the recovery worked with CoworkVMService still Running

I did not stop the service. It was Running throughout, and the unload plus launch succeeded anyway. Combined with @J3TH4CK's field result that stopping it 4× didn't help, that looks conclusive from both directions: the service is neither the pinner nor part of the cure. Probably worth dropping it from any future mitigation guidance, since "stop CoworkVMService" is the first thing most people (myself included) reach for.

Detail 2 — stale silos accumulate across update cycles

Eight mounts across four distinct silo IDs, not one. Only the most recent transition had wedged the app, so the other three were residue from earlier update cycles that had been cleared by sign-out/reboot at the time — the reboot got the app running again but evidently left hive mounts behind. Anything that enumerates and tears down "the outgoing silo" should probably sweep all stale Claude entries in hivelist, not just the previous epoch's.

Detection signal

Worth noting for anyone scripting this: hives present in hivelist while zero package processes are running is a clean, positive test for the wedged state, checkable before attempting a launch. Every process-based check is a false negative here, as this thread has established several times over. Reading HKLM\SYSTEM\CurrentControlSet\Control\hivelist for Claude matches needs no elevation.

Thanks @bugale — that PoC turned a sign-out into about five seconds.

fritzschlechter · 1 month ago

Another independent occurrence with the identical signature, on a machine that hits it on every stealth update — adding it mainly because the app-side main.log and the AppModel-Runtime log line up to the second, and because this machine is a Squirrel→MSIX migration (non-virtualised profile), which changes where the orphaned chrome-native-host.exe lives.

Environment: Windows 11 Enterprise 10.0.26100, Claude_pzs8sxrjxfjjc, SignatureKind: Developer. Seven updater-driven relaunches between 2026-08-25 and 2026-09-03 (1.34493.1 → 1.37937.0 → .1 → .3 → 1.40609.0 → .1 → 1.44121.2), every one via [stealth-update] Triggering stealth update after idle timeout. main.log reports [MSIX] Filesystem not virtualized — …\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude absent (likely Squirrel upgrade), so the app's profile is the plain %APPDATA%\Claude, not the LocalCache path.

Timeline, 2026-09-03 (local), 1.40609.1 → 1.44121.2

| Time | Source | Event |
|---|---|---|
| 01:29:54–01:30:23 | AppXDeploymentServer | Add of 1.44121.2 with DeferRegistrationWhenPackagesAreInUse; event 658 deferred registration (1.40609.1 still running) |
| 01:31:51 | main.log | [remote-tools-device] keep-awake held — a Cowork remote-tools session was still active |
| 01:40:53 | main.log | [stealth-update] Triggering stealth update after idle timeout → [stealth-relaunch] Saved navigation history (43 entries) |
| 01:40:54 | main.log | beforeQuitForUpdate handler fired, going down for update — last line for 5 h 46 min |
| 01:40:54–01:40:57 | AppXDeploymentServer | RegisterByPackageFamilyName … ForceApplicationShutdownOption; CoworkVMService terminated; Register event 400 "finished successfully" |
| 01:40:56 | AppModel-Runtime | 217 Destroyed container for 1.40609.1; 210/211 new container created for 1.44121.2 and process 31836 added; then 215 0x80070020 … converting the job ×2 and 208 0x80070020 [LaunchProcess] — i.e. the updater's own immediate relaunch already fails |
| 07:15:56–07:18:20 | AppModel-Runtime / AppXDeploymentServer / SCM | Four manual launches. Each: 215/208 0x80070020, then RegisterByPackageFullName … RepairAppRegistrationOption, event 649 "ACLs repaired successfully … Register next time should succeed", SCM 7045/7040 (CoworkVMService reinstalled, auto→disabled). SCM 7034 "Claude service terminated unexpectedly" at 07:16:32. Nothing written to main.log by any attempt |
| 07:19:21 | System 1074 | User-initiated restart |
| 07:19:23 | AppModel-Runtime | 217 Destroyed container {290A7BD3-…} for 1.40609.1 — the old version's container was alive the whole time, matches @jerds19's observation |
| 07:27:01 | AppModel-Runtime / main.log | 201 process created; Starting app { appVersion: '1.44121.2' }, [event-loop-stall] update relaunch (stale marker) stands in for an unheard suspend |

The previous update on 2026-09-01 (1.40609.0 → 1.40609.1, stealth relaunch at 21:17:55) produced the same 215/208 0x80070020 triples at 21:19:30, 21:22:02 and 21:22:03, and the app also only came back after a reboot the next morning.

Data points for the analysis above

  • Non-virtualised profile: on this machine the Chrome native host runs from %APPDATA%\Roaming\Claude\ChromeNativeHost\chrome-native-host.exe, not from Packages\…\LocalCache\Roaming\Claude\ChromeNativeHost. Any cleanup that keys on the LocalCache path would miss it here. It is spawned by Chrome at logon, before the app itself starts (today: 07:24:34 vs app start 07:27:01).
  • Every startup on the new version also logs [Chrome Extension MCP] Failed to copy native host binary: Error: EBUSY: resource busy or locked, copyfile '…\WindowsApps\Claude_<ver>\app\resources\chrome-native-host.exe' -> '…\Roaming\Claude\ChromeNativeHost\chrome-native-host.exe', i.e. the app tries to overwrite the running native host on each launch and can't — consistent with it never being reaped.
  • hivelist after the reboot still carries eight Claude mounts across four silo ids (Silo5b669f8d… user_sid/user_classes/software/com, plus three older …com/…software entries), matching @cplagz's note that stale silos accumulate across cycles even after a reboot "fixes" the launch.
  • The "idle timeout" that triggers the stealth relaunch fired while a Cowork remote-tools session was holding keep-awake; whatever the idle heuristic is, it does not consider active remote/Cowork sessions. That relaunch cost an in-flight response (~3 h of review work) on an earlier cycle on this machine.

Redacted extracts (main.log excerpt covering all seven cycles, AppXDeploymentServer, AppModel-Runtime with the hivelist dump, SCM and Application events, environment) attached in a follow-up comment.

Vladimir321123 · 1 month ago

Another machine with the same signature, adding two data points the thread does not have yet: (1) the app's own child-process counter in main.log predicts which stealth updates will wedge, and (2) on this machine the likely job member is a plugin daemon that a SessionStart hook starts inside the container (claude-mem worker), i.e. the same mechanism as the git fsmonitor--daemon case in #91763 with a different culprit.

Environment: Windows 11 Home 10.0.26200, Claude_pzs8sxrjxfjjc (SignatureKind Developer, Squirrel-to-MSIX migrated profile: main.log says [MSIX] Filesystem not virtualized), bundled Claude Code 2.1.255 / 2.1.258, ClaudeStartup StartupTask disabled. Nine stealth updates between 2026-08-18 and 2026-09-03, four of them ended in "dead until reboot".

1. [process-memory] children(N) right before the stealth update predicts the outcome

main.log writes [process-memory] ... electron(N)=... children(N)=... about once a minute. Last such line before each [stealth-update] Triggering stealth update after idle timeout:

| Date (local) | Transition | children(N) before quit | Result |
|---|---|---|---|
| 08-18 15:24 | 1.30096.5 to 1.32352.1 | 0 | relaunched in 31 s |
| 08-19 02:22 | 1.32352.1 to 1.32885.1 | 0 reported (see note) | dead until reboot on 08-20 14:45 |
| 08-21 02:01 | 1.32885.1 to 1.34493.0 | 0 | relaunched in 1 s |
| 08-21 06:11 | 1.34493.0 to 1.34493.1 | 0 | relaunched in 1 s |
| 08-26 15:22 | 1.34493.1 to 1.37937.1 | 0 | relaunched in 34 s |
| 08-27 02:33 | 1.37937.1 to 1.37937.3 | 0 | relaunched in 35 s |
| 08-29 12:10 | 1.37937.3 to 1.40609.0 | 0 reported (see note) | dead until reboot 12:18 |
| 09-01 20:35 | 1.40609.0 to 1.40609.1 | 164 (2956 MB) | dead until reboot 20:48 |
| 09-03 01:01 | 1.40609.1 to 1.44121.2 | 118 (3821 MB) | dead until reboot 16:37 |

Note: the first build that ever reported a non-zero children value here is 1.40609.0, so the two zeros on failed cycles may be the counter rather than reality. Every relaunch that succeeded had no session tree; both cycles with a measured large tree failed. In the failed cycles the log ends at beforeQuitForUpdate handler fired, going down for update / Windows session ending (close-app) - quitting the app and the next line is Starting app after the reboot, with update_relaunch_marker: 'stale'.

2. AppModel-Runtime/Admin timeline for 2026-09-03 (local time)

  • 01:01:05 main.log stealth update triggered, 01:01:06 quitting the app.
  • 01:01:38 event 217 destroys container {B8B23368-...} of 1.40609.1. That container was created at logon (09-01 20:48:38) with cowork-svc.exe as the process added in 210/211, so it is the packaged service's container, not the app's. On this machine every epoch has two containers, one for CoworkVMService and one for the app; worth knowing when pairing 210/217.
  • 01:01:39 event 67 (0x5) for 1.40609.1, event 215 0x80070020 ... converting the job for 1.44121.2, then 210/211 create {DC0D3BB7-...} with process 26116, then 215 and 208 0x80070020 [LaunchProcess] at 01:01:40. Nothing reaches main.log.
  • 16:36:24 manual launch: 215 x2 + 208. 16:36:26: 210/211 {BE2239B1-...} + 215 x2 + 208. TWinUI 1621 reports both activations as successful.
  • 16:36:41 event 217 for {B8B235C3-...}, the app container of 1.40609.1 created on 09-01 20:50:20 with the app's main process 27012. It stayed alive 15.5 hours after the app quit and was destroyed only by the user's reboot at 16:37.
  • 17:08:18 first launch after reboot: event 201, normal start.

The 09-01 cycle is identical: old app container {31E03264-...} of 1.40609.0 destroyed only at 20:47:46 during the reboot, 215/208 for every launch attempt at 20:36:25 and 20:46:33-34.

3. What is in the container job on this machine, and what is not

Checked on the healthy post-reboot instance with IsProcessInJob(hProcess, NULL) and GetPackageFullName:

  • The Code-tab CLI (%APPDATA%\Claude\claude-code\2.1.258\claude.exe, spawned by the main process) is in a job, and so is every child it spawns: ssh.exe, cmd.exe, node.exe, pwsh.exe, uv.exe, conhost.exe and third-party MCP server executables. None of them has package identity (APPMODEL_ERROR_NO_PACKAGE). That is the #91763 mechanism, and it covers every MCP server, shell and SSH tunnel a session starts.
  • Children spawned from the Electron node.mojom.NodeService utility processes (pwsh.exe, conhost.exe) are not in a job. So the app already has a spawn path that breaks away from the container; the Code-tab CLI just does not use it.
  • Candidate pinner here: bun.exe ... claude-mem/.../worker-service.cjs --daemon (the claude-mem plugin's worker). It was started 11 s after the app by a SessionStart hook of a Code-tab session, its parent is already dead, it is in a job, has no package identity, and is designed to outlive sessions. git fsmonitor--daemon is not present here (core.fsmonitor unset), so #91763's culprit is one instance of a general class: anything a hook or MCP server daemonises inside the container survives ForceApplicationShutdown and pins the old silo. Orphaned MCP server processes from long-closed sessions (a dozen telegram-mcp python processes on 08-25) have also been seen on this machine, so there is never a shortage of job members.

Suggestions

  1. Gate the stealth relaunch on the counter the app already has: do not quitForUpdate while children(N) > 0, or terminate the app's own container job members first.
  2. Spawn the Code-tab CLI (and therefore everything it spawns) with CREATE_BREAKAWAY_FROM_JOB, the way the utility-process path already ends up outside the job.
  3. When the post-update relaunch hits 215/208, surface it instead of dying silently; the user only sees the "Another program is currently using this file" dialog and reboots.

Next occurrence I will kill the orphaned job members (dead parent + in job + no package identity) before trying @bugale's hive unload, and report which of the two clears it.

gmyuval · 1 month ago

Another machine with the same signature (1.40609.1.0 → 1.44121.4.0 on 2026-09-03, and 1.40609.0.0 → 1.40609.1.0 on 2026-09-01), adding one data point I don't see in the thread yet: the app-side reason the process tree survives.

main.log shows the stealth-update quit runs none of the cleanup steps a normal quit runs. Same day, same install:

2026-09-03 10:02:37 [info] Quitting app on main window close since tray is disabled
2026-09-03 10:02:37 [info] beforeQuit: handler fired, going down
2026-09-03 10:02:37 [info] Running onQuitCleanup: mcp-shutdown
... 31 "Running onQuitCleanup: ..." lines in total, among them local-mcp-server-cleanup,
    plugin-mcp-server-cleanup, direct-mcp-host-shutdown, lam-session-stop-all, pty-host-shutdown ...
(AppModel-Runtime 217: container destroyed 3 s later)

2026-09-03 21:04:13 [info] [process-memory] trigger=interval ... electron(12)=2063MB children(57)=2700MB ...
2026-09-03 21:04:26 [info] [stealth-update] Triggering stealth update after idle timeout
2026-09-03 21:04:27 [info] beforeQuitForUpdate handler fired, going down for update
2026-09-03 21:04:27 [info] Windows session ending (close-app) - quitting the app
(0 "Running onQuitCleanup" lines; the next line is "Starting app" at 22:01:01, after a sign-out)

Counts over the last four quits on this machine: normal quits 31 and 31, stealth-update quits 0 and 0. The normal path is where MCP servers, plugin servers, LAM sessions and pty hosts are shut down, so on the stealth path the 57 children are left to Restart Manager, which waited its full 30 s (Gap: 33438 ms in the AppXDeploymentServer performance summary) and did not get all of them: the old version's container {CEA01A24-…} was destroyed at 21:44:38, one second before the logoff notification, 40 minutes after the package had been swapped.

Corroborating the rest of the thread: 215/208 0x80070020 on every launch (13 attempts, six automatic repair registrations all "finished successfully"), the PackagedService container created fine each time, sign-out sufficed (no reboot), ClaudeStartup StartupTask disabled here, Squirrel-migrated profile ([MSIX] Filesystem not virtualized). No git fsmonitor--daemon on this machine (core.fsmonitor unset everywhere); the candidates here are MCP servers started through uvx/node wrappers and a Codex node_repl.exe runtime, all long-lived detached children of the Code tab.

One more thing, for whoever looks at the updater side: the deferred registration attempted at a normal launch (Stage + RegisterByPackageFullName, no force option) failed 0x80073D02 because cowork-svc.exe counts as the package being in use, so the deferred path can never complete on a machine where the service auto-starts, and every update goes through the forced stealth path (#89599 covers that side).

Suggested fix on the app side: run the onQuitCleanup chain (or at least its child-process shutdown steps) on beforeQuitForUpdate and wait for the tree to end before the update proceeds. Full timeline, event-log exports and an unprivileged \Container_* job enumerator available on request.

JohnnyRayWork · 1 month ago

Adding one small diagnostic detail I don't see in the thread, from another 10.0.26200 machine that hit this on 1.40609.1.0 -> 1.44121.4.0 (2026-09-03, app dead 13:32->17:00, cleared by reboot). @bugale's hive analysis matches what I captured.

Microsoft-Windows-TWinUI/Operational 1621 reports every failed activation as a success. During the 3.5h outage an external watchdog re-activated the app via shell:AppsFolder\Claude_pzs8sxrjxfjjc!Claude every 10 minutes, 21 times. All 21 died at AppModel-Runtime 215/208 0x80070020 — but each logged:

1621  Activation of app Claude_pzs8sxrjxfjjc!Claude attempted.
      Execution state: Attempted activation of the app, 0, The operation completed successfully.

because 1621 reports only that the activation request was dispatched.

Consequence: there is no success/failure signal at the activation call site. Start-Process shell:AppsFolder\... returns 0, TWinUI says success, and the app simply never appears. Any automated recovery has to poll for the process and then read AppModel-Runtime 208/215 to learn why.

That's an argument for surfacing 0x80070020 instead of dying silently — it would turn "clicking the icon does nothing" into something diagnosable.

I also confirm the two refuted mitigations on this machine: CoworkVMService was Running throughout the failure (and is Running now, post-recovery), and no WindowsApps\Claude_* process existed during it.

alangz75 · 1 month ago

Same failure mode here on Windows 11 (26200), MSIX install from the Store.

Three idle-triggered stealth updates in two days, two of which needed a reboot:

| KST | Version | Outcome |
| --- | --- | --- |
| 2026-09-03 13:29 | 1.40609.1.0 → 1.44121.4.0 | relaunched on its own ~20s later |
| 2026-09-04 15:09 | 1.44121.4.0 → 1.46388.1.0 | never came back; reboot |
| 2026-09-04 21:07 | 1.46388.1.0 → 1.46388.2.0 | never came back; reboot |

All three start the same way in %LOCALAPPDATA%\Claude\logs\main.log:

[stealth-update] Triggering stealth update after idle timeout
beforeQuitForUpdate handler fired, going down for update

On the third one Windows surfaced the sharing violation directly as a dialog —
"이 파일을 다른 응용 프로그램에서 사용 중입니다" (this file is in use by another
application), pointing at C:\Program Files\WindowsApps\Claude_1.46388.2.0_....
That matches the 0x80070020 in this issue.

Data point on the orphan mechanism

I think I can name one of the survivors on my machine. I use the Code tab heavily,
and Claude Code had started a long-running node dev server for my project. Its
parent chain is:

node.exe(18404)  <-  bash.exe(20464)  <-  (PID 1716, already gone)

So it is orphaned but alive, is not named claude.exe, and would not show up under
Task Manager's "Claude" group — exactly the profile described in this issue. A server
like that was running during the 21:07 failure.

Caveat so this is not overstated: I captured that parent chain from a freshly started
server after recovery, not from the failing moment, and I did not verify job-object
membership. So it shows these processes do get orphaned and outlive their parent, not
that this specific one held the container.

The service, which behaves differently here than in the note above

A comment above lists CoworkVMService as a refuted mitigation because it stayed
Running throughout the failure. On this machine it did not. The System log shows
7034 ("Claude 서비스가 예기치 않게 종료되었습니다") in both failures, at 15:28:09 and
21:17:34 KST, and not at all on 9/3 when the update succeeded. So the service may
behave differently across machines rather than being uniformly irrelevant.

Related, and I have not seen it mentioned in the thread — this is about the service's
recovery configuration, not about whether it was running. The service is reinstalled
on every update (7045/7040 pairs) and each time logs to the Application log:

Claude VM Service: failed to configure SCM recovery actions;
if the service crashes it will not restart until the machine reboots:
open service: Access is denied.

sc qfailure CoworkVMService confirms zero failure actions are registered
(RESET_PERIOD: 0). The service runs as LocalSystem but is apparently configured by a
caller without the rights to set recovery actions. Where the service does die, that
would be a second, independent reason a reboot is the only recovery — Windows is never
asked to restart it.

Environment note

Offered as data rather than a hypothesis: a VPN (ProtonVPN) auto-connects at boot, so
the app always launches with it up, and I disconnect it shortly after I start working.
I have no evidence tying that to this failure — the 0x80070020 path is local file
locking, not network — but the app consistently outlives a network transition on this
machine, which may be worth knowing if anyone else here has the same pattern.

Ruled out on my side, in case it saves anyone time: no BSOD or Kernel-Power events, no
Resource-Exhaustion warnings, app memory ~2.2 GB of 32 GB, 65 GB free disk.

alangz75 · 1 month ago

Fourth occurrence this morning, and it adds a few things to what I posted above.

1.46388.2.0 → 1.46388.3.0, 2026-09-05 09:25:46 KST, same [stealth-update] Triggering stealth update after idle timeout. Same file-in-use dialog (0x80070020) pointing at C:\Program Files\WindowsApps\Claude_1.46388.3.0_.... Three of the four have now needed a reboot.

Killing claude.exe from Task Manager did not help. I tried that before rebooting — the app still would not launch. Whatever holds the container is not claude.exe, which lines up with the orphan description in this issue: not named claude.exe, not in Task Manager's "Claude" group. Only the reboot recovered it.

No CoworkVMService crash this time. In the two failures I described earlier the System log had 7034 for that service; this time it does not — only the usual 7045/7040 reinstall churn at 09:26:23, 09:28:27 and 09:28:58. So the service dying is not required for this failure. It may be a second, independent way to get stuck rather than the cause of this one; I'd treat my earlier service note as a separate issue from the container problem.

On my dev server, which I offered above as a candidate survivor — the evidence now points the other way. This time the app's own shutdown killed it:

09:25:46 [CCD] Killing 1 PTY process tree(s) on quit
09:25:46 Stopping shell PTY for session local_...
and after recovery there were zero node.exe processes. So at least for a process started by Claude Code in a normal terminal session, the PTY tree kill does reach it. Please discount that part of my earlier comment.

One environment variable I can now rule out. In my earlier comment I noted that a VPN auto-connects at boot and that I usually disconnect it once I start working, so the app normally outlives a network transition. Today I kept the VPN connected the whole time — through launch, through the update, through recovery — and the failure was identical. So the network transition is not involved.

Aaron-905 · 1 month ago

Another independent reproduction, with a different survivor and a no-reboot, no-elevation recovery that lines up with #91763 and #92167.

Environment: Windows 11 Pro 10.0.26200, Claude Desktop MSIX Claude_pzs8sxrjxfjjc, standard non-admin user. Update 1.46388.2.0 -> 1.46388.3.0 (the fourth stealth relaunch in 48 h on this machine: .1, .2, .3 all shipped within a day).

Sequence (local time, 2026-09-05):

06:52:59  AppXDeployment 400: 1.46388.3.0 Add finished successfully (deferred, 1.46388.2.0 running)
09:10:56  main.log: [stealth-update] Triggering stealth update after idle timeout
09:10:56  main.log: [CCD] Killing 2 PTY process tree(s) on quit
09:10:56  main.log: beforeQuitForUpdate handler fired, going down for update
09:10:56  AppXDeployment 603: RegisterByPackageFamilyName, ForceApplicationShutdownOption
09:11:29  Application Hang 1002: conhost.exe (one of the killed PTYs) "stopped interacting and was closed"
09:11:29  AppModel-Runtime 217: container {2414F880-...} for 1.46388.2.0 destroyed   <- the packaged service's container
09:11:30  AppModel-Runtime 215: 0x80070020 ... error was encountered converting the job
09:11:30  AppModel-Runtime 208: 0x80070020 ... [LaunchProcess]
          main.log ends here; no boot line follows. App dead until 17:40.

Every manual launch afterwards (Start menu and shell:AppsFolder\Claude_pzs8sxrjxfjjc!Claude) repeated 215/208, and each attempt stopped and reinstalled CoworkVMService (System log 7040/7045) without effect. winget (DesktopAppInstaller, also a Desktop AppX) launched fine from the same session, so the container machinery itself was healthy.

What was holding it. The second container of the old version, {2414F87F-...} for 1.46388.2.0 (the one claude.exe itself ran in, created 00:52:51 at the previous relaunch), never got its 217. The Helium hive was still mounted and file-locked:

hivelist: \REGISTRY\WC\Silo08384da3-...user_sid  ->  ...\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat
[IO.File]::Open(User.dat, 'Open', 'Read', 'None')  ->  "being used by another process"

No process carried the package identity (tasklist /apps empty for Claude). Enumerating processes whose parent PID is dead and checking IsProcessInJob(h, NULL) found the survivor:

PID 7824  node.exe  "C:\Program Files\nodejs\node.exe" serve.mjs
created 07:00:50 (inside the old container's lifetime), parent dead, IsProcessInJob = true, no package identity

It was a static dev server started by a Claude Code session hosted in the desktop app (a Bash tool call that backgrounded it). Not a PTY tree, so beforeQuitForUpdate never touched it.

Stop-Process -Id 7824 -> AppModel-Runtime 217 for {2414F87F-...} fired the same second, the User.dat open-for-exclusive test immediately succeeded, and the next launch produced 201 (Created process ... 1.46388.3.0). The app is running normally on 1.46388.3.0 now. No reboot, no elevation, no service changes.

A red herring worth noting: an orphaned chrome-native-host.exe (spawned by Chrome for the browser extension on 09-03 from the 1.46388.1.0 cache path) was also alive. Killing it cleared the [Chrome Extension MCP] Failed to copy native host binary: EBUSY errors in main.log but did not release the container. The container holder was only the non-PTY node.exe child.

Quick non-admin triage that worked here, for anyone hit by this before a fix ships:

# 1. Is the hive still pinned?
$h="$env:LOCALAPPDATA\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat"
try { ([IO.File]::Open($h,'Open','Read','None')).Close(); 'free' } catch { 'LOCKED' }

# 2. Orphans (dead parent) created since the app's last successful boot, and whether they sit in a job
Add-Type -Namespace W -Name J -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessInJob(IntPtr h, IntPtr job, out bool r);'
$all = Get-CimInstance Win32_Process; $ids = @{}; $all | % { $ids[$_.ProcessId] = $_ }
$all | ? { -not $ids.ContainsKey($_.ParentProcessId) } | % {
  $r=$false; try { [W.J]::IsProcessInJob((Get-Process -Id $_.ProcessId).Handle,[IntPtr]::Zero,[ref]$r) | Out-Null } catch {}
  [pscustomobject]@{ Pid=$_.ProcessId; Name=$_.Name; Created=$_.CreationDate; InJob=$r; Cmd=$_.CommandLine } } | sort Created | ft -Auto

# 3. Stop the candidates created inside the old container's lifetime one at a time, re-run step 1 after each.

Same asks as the thread: break Claude Code's non-PTY children out of the container job (or terminate the whole package job on beforeQuitForUpdate), and don't fire the stealth relaunch while the old container still has members. The generic "Another program is currently using this file" dialog should at least name the PID that is pinning the container.

youhorn · 1 month ago

Another occurrence on current versions (1.46388.4.0 → 1.49585.0.0, 2026-09-09), with one data point this thread doesn't have yet: self-recovery without reboot, sign-out, or any manual cleanup — the wedge cleared itself ~6 minutes after the failed launch, as soon as the old container was destroyed.

Environment: Windows 11 Education 10.0.26200, Claude_pzs8sxrjxfjjc, Claude in Chrome connected (chrome-native-host.exe alive since before the update).

Timeline (local time, from event logs):

  • 20:07:46 — old 1.46388.4 launched; AppModel-Runtime event 210, container {13C6C86A-…}
  • 20:08–20:09 — 1.49585.0 staged; event 658 deferred registration ("because 1.46388.4 is still running")
  • 21:45:42 — app restart; RegisterByPackageFamilyName + ForceApplicationShutdownOption; event 400 register finished successfully in 1.1 s
  • 21:45:43 — relaunch fails twice: events 215/208 0x80070020 … error encountered converting the job; plus event 67 failing with 0x5 while modifying the old package's status
  • 21:51:47 — the old version's container {13C6C86A-…} (alive since 20:07) is destroyed on its own
  • 21:52:26 — very next launch succeeds normally; the app has been fine since

Agrees with the thread on the key mechanics: successful (re)registration does not clear the wedge (register succeeded one second before the 215/208 pair), and the failure window is exactly [forced registration → old-container teardown].

The pre-update chrome-native-host.exe instance was gone by the time I inspected (the current one respawned at 21:54:05, seconds before the app's next start); I did not capture what ended it, so I can't say whether its exit is what released the container here — but its lifetime spanning the update is consistent with the forensic snapshot above.

Implication for the fix: the wedge is not always terminal. A post-update launcher that detects 0x80070020 and retries with backoff after awaiting/verifying old-container teardown would have made this occurrence completely invisible to the user — instead the user got a launch error that resolved itself minutes later for no visible reason.

15230745073 · 1 month ago

Three additions the thread doesn't have yet: a negative-control machine, the
event signature that separates a wedging update from a non-wedging one, and an
app-side fix with an existence proof. Everything about the Helium hives / silo
(@bugale, @cplagz) and the ruled-out mitigations (@J3TH4CK) matches here and I won't
restate it.

Environment: Windows 11 Pro 10.0.26200, Claude_pzs8sxrjxfjjc (SignatureKind:
Developer
), Cowork enabled, Claude Code used inside the desktop app daily.
Frequency: 10 of the last 12 updates wedged (2026-08-26 → 09-14, 1.34493.1 →
1.52386.6). Recovery each time: sign out and back in (~4 s), nine times.

1. A second machine that never wedges — same day, same version bump, opposite outcome

A colleague's machine runs the same MSIX package family, the same versions, and the same
[stealth-update] code path (his main.log has 8 × stealth-update / 8 ×
beforeQuitForUpdate). Over a 12-day AppModel-Runtime/Admin window he took 4 updates
(09-07, 09-09, 09-11, 09-14) and logged 0 events 215
.

The cleanest pair is 2026-09-14, 1.52386.3.0 → 1.52386.6.0, both machines the same day:

| | mine | colleague |
|---|---|---|
| outcome | 215 ×2 + 208, dead until sign-out | launched normally, 0 × 215 |
| update path | [stealth-update] | [stealth-update] |
| window total | 12 updates / 10 wedged | 4 updates / 0 wedged |

Same app version, same version bump, same day, same code path, opposite results — which
rules out the package/payload and leaves a per-machine timing difference.

2. What actually separates them: which containers get destroyed inside the Register window

The discriminator is visible in AppModel-Runtime/Admin event 217 during the
RegisterByPackageFamilyName window. Container ownership is readable from the container
name in the 210 event data: <pkgfullname>-S-1-5-21-… is the per-user container,
<pkgfullname>-PackagedService is the packaged service's.

Colleague, 09-11 1.49585.0.0 → 1.52386.0.0 — succeeds:

09-10 08:49:23  210  user container  (1.49585.0.0)      ← created at app launch
09-11 09:38:23  [stealth-update] Triggering              (app log)
09-11 09:38:24  beforeQuitForUpdate                      (app log)
09-11 09:38:24  603  RegisterByPackageFamilyName + ForceApplicationShutdownOption
09-11 09:39:01  217  ×2 — BOTH 1.49585.0.0 containers destroyed (user + service)
09-11 09:39:03  210  user container  (1.52386.0.0)
09-11 09:39:14  201  app launched.   0 × 215

That old user container had been alive 24 h 49 min.

Mine, 09-14 1.52386.3.0 → 1.52386.6.0 — wedges:

09-14 08:32:08  210  user container 93F98DAF (1.52386.3.0)
09-14 10:30:53  beforeQuitForUpdate                      (app log)
09-14 10:30:54  217  — service container ONLY
09-14 10:30:55  215 / 10:30:57  215 + 208
                 → 93F98DAF still Live; destroyed only at 10:31:49 sign-out

So: old user container destroyed inside the Register window ⇒ success; only the service
container destroyed ⇒ wedge.
The most likely reading is that
ForceApplicationShutdownOption only tears the container down when it still finds the app
process alive — on my machine the app is gone within ~1 s of beforeQuitForUpdate, on his
it is still there ~37 s later. I can't prove the app-exit latency directly from logs, but
the 217 signature itself is checkable by anyone in this thread.

3. The container is a logon-session object — it is not "pinned" by a process

Two measurements that I think reframe the "who is the pinner" question:

  • The per-user container survives ordinary app exits for the whole logon session. On my

machine 085C318C lived 2026-09-09 09:28 → 09-11 10:45 (sign-out) — 2 days, across two
full app quits and three launches
; each relaunch only joined it (event 211 with no new
210). A second one lived 10 h 36 min and outlived the app's quit by 2 h 20 min. The
colleague's lived 24 h 49 min. This looks like normal warm-container behaviour, not a leak.

  • At wedge time the container can be completely empty. An automatic snapshot taken 8 s

after the 215s on 09-11 enumerated package identity via GetPackageFamilyNameFromToken
over all 427 processes: zero processes with Claude_* identity, and no claude.exe
anywhere in the process table.

So "an orphaned member pins the job" isn't required for the wedge. Consistent with that,
on this machine:

| candidate | result |
|---|---|
| chrome-native-host.exe | not running at the 09-05 and 09-09 wedges (its own log shows it had exited hours earlier) |
| children(N) / open sessions | wedged with only 2, 3 and 5 PTY trees; meanwhile non-update quits with 8–9 trees released the container in 10–12 s |
| RuntimeBroker.exe (old package identity, parent svchost) | present at 2 of 3 captured wedges, absent at the third → not necessary |

I'd read @J3TH4CK's chrome-native-host and #91763's fsmonitor--daemon as real job
members that make things worse (they extend the epoch), but not as the necessary cause.

4. The app-side fix, with an existence proof

Current sequence is: [stealth-update] → app quits itself → `RegisterByPackageFamilyName

  • ForceApplicationShutdownOption` → app relaunches. When the app wins that race, the

forced shutdown has nothing left to shut down, the old user container is never torn down,
and the new version's container setup hits the still-mounted hives.

Suggested change: don't self-quit before the register — issue the register while the app
is still running and let ForceApplicationShutdownOption terminate it.
That path
destroys the old container as part of the shutdown, which is exactly what makes the
colleague's machine succeed 4/4.

It also already worked once on my machine, by accident: on 2026-09-03 08:14 the
deployment's two 217s landed 0.87 s after beforeQuitForUpdate — the app was caught
mid-quit, both containers went down, and the update completed with 0 × 215. That is the
same code path succeeding when the shutdown wins the race.

This needs no Windows-side change and no user workaround, and it would make the
NtUnloadKey2 / sign-out recipes in this thread unnecessary rather than mandatory.

AleksBiSonic · 29 days ago

Another machine, on 2.110.0.0. 1.49585.0.0 -> 2.110.0.0 on 16/09/26, same wedge. I am adding only what is not in the thread yet. The hive and silo side from @bugale and @cplagz matches and I will not repeat it. Every line below is a direct read from this machine's logs. Anything I did not check says so.

Environment: Windows 11 Home 10.0.26200, Claude_pzs8sxrjxfjjc, SignatureKind Developer, [MSIX] Filesystem virtualization active, bundled Claude Code 2.1.260 -> 2.1.271. One Code tab session open with seven stdio MCP servers. I rebooted before I had read this thread, so I have no hivelist or job snapshot from the wedged state.

1. Timeline (16/09/26, local)

| Time | Event |
|---|---|
| 09:19:12 | During-logon registration of 1.49585 (staged 09/09/26; app closed since 09/09/26 17:07 after a full clean quit). ForceTargetApplicationShutdownOption, 613 gap 15 ms, one 217 for the service container. App launched 09:19:14. No 215. |
| 09:19:47 | App updater fetched 2.110 from downloads.claude.ai. 09:20:18 and 09:28:40: 638/658 deferred, app running. |
| 09:41:07 | Windows retried the deferred registration itself, mid-session, during a Windows Update pass (event 821; WER StoreAgentInstallFailure1 from Update;MoUpdateOrchestrator). 0x80073D02, app running. |
| 09:48:03 | Last [process-memory]: electron(13) children(14). |
| 09:48:19 | [stealth-update] Triggering stealth update after idle timeout, Killing 1 PTY process tree(s), beforeQuitForUpdate, Windows session ending (close-app). Last main.log line until after the reboot. |
| 09:48:20 | 603 RegisterByPackageFamilyName + ForceApplicationShutdownOption. |
| 09:48:55 | 217 for the service container only. Event 67 0x5 on the old package. Application Hang 1002 on conhost.exe. |
| 09:48:57 | 400 finished successfully. 613: overall 37,328 ms, gap 35,390 ms. 215 x2 + 208 0x80070020 for 2.110. Nothing reached main.log. |
| 10:55:55, 10:56:28 | Two manual launches. Each: Register with RepairAppRegistrationOption succeeded, 215 x2 + 208, TWinUI 1621 "completed successfully". |
| 10:57:03 | Reboot. 217 for the app's user container, created 09:19:14, 68 minutes after the app quit. The 09:28 relaunch had joined that container (211, no new 210). |
| 11:11:14 | Starting app { appVersion: '2.110.0' }. Fine since. |

2. What this adds

  • @15230745073's 217 signature holds: inside the register window only the service container went down, and the user container lived until the reboot.
  • The register did not find an empty container. Gap 35,390 ms is the full shutdown wait, same as @gmyuval's 33,438 ms. It waited on something, then still only took the service container. That fits surviving non-PTY job members (@Vladimir321123, #92167) better than an app that exited in a second. children(14) at quit, one PTY tree killed. The non-PTY members here are the MCP servers: each stdio server runs as cmd.exe -> node.exe (npx) -> cmd.exe -> node.exe with its own conhost, one set per open session. Which member survived: not checked.
  • Deferred registration is retried inside a live session when the Update Orchestrator runs a Store pass. @J3TH4CK saw no retry with the app closed. Here Windows retried at 09:41:07 with the app open and failed 0x80073D02. Seven minutes later the idle timer did it the wedging way. The week-old 1.49585 only applied at today's logon, which matches the "left for a logon" reading.
  • The process a user will kill. Before rebooting I saw one small "Claude" entry in Task Manager, about 15 MB. End task killed it. Claude still did not open. That fits cowork-svc.exe: its container ran 09:48:57 to 10:55:56 and every Register restarts it. PID not recorded, so this is a match on size, name and timing only. It agrees with the thread: the service is neither the pinner nor the cure.
  • chrome-native-host.exe ran from Packages\...\LocalCache\Roaming\Claude\ChromeNativeHost\ from 09:19:50 to the reboot, per its own log. Present across the transition. Not tested as the holder.

3. One note on the dialog

I read the 0x80070020 text as "the application is being used elsewhere" and spent my first pass on the single-instance lockfile. Dead end. If a maintainer changes one thing first, make that dialog name the container problem.

alexonishchuk91-oss · 27 days ago

Another repro on 2.110.0.0 (1.52386.6.0 → 2.110.0.0), Windows 11 Home 10.0.26200, standard non-elevated user. The same machine hit this twice in 5 days, both times with the same kind of survivor, so I'm posting only the concrete data points.

Survivor both times: a background server started by the Desktop Claude Code session's Bash tool

  • 2026-09-15 09:21 — the agent ran cd work && (python serve.py > serve.log 2>&1 &) through the Bash tool (Git Bash) in a scratch workspace. The subshell exits immediately, so python.exe is orphaned (parent PID gone) but stays in the app's job.
  • 2026-09-16 18:32:14 — [stealth-update] Triggering stealth update after idle timeout → [CCD] Killing 2 PTY process tree(s) on quit → beforeQuitForUpdate handler fired, going down for update. The python server (listening on 127.0.0.1:8765) was not in a PTY tree and survived.
  • The old container {C83592A4-…} (1.52386.6.0, event 210 at 2026-09-15 07:26:24) never got a 217. hivelist kept \REGISTRY\WC\Silo…user_sid → …\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat mounted.
  • Every launch of 2.110.0.0 failed with 215 ×2 + 208 0x80070020 (2026-09-16 18:33:15; 2026-09-17 18:54:49, 18:54:51, 19:04:07, 19:04:09).
  • The first occurrence (2026-09-12, 1.52386.0.0 → 1.52386.3.0) had the same shape: the survivor was python -m http.server 8765, also started in the background by a Desktop Code session.

Detection: the survivor has no package identity (GetPackageFullName returns no package, tasklist /apps doesn't list it), so it can't be found by package. What worked: an orphaned process (dead parent) created after the old container's event 210, while the old version was running.

Negative result: Stop-Service CoworkVMService (allowed without elevation) did not help. The next launch still failed with 0x80070020, and the service was started again automatically.

Recovery without reboot or elevation: Stop-Process -Id <python pid> → event 217 for {C83592A4-…} at 19:11:04, Helium\User.dat gone from hivelist, next launch at 19:11:15 → 210/211/201, and the app booted normally.

Suggestion: quit-for-update only kills PTY trees, but background processes started by the Bash tool ((cmd &)) remain in the app's job. Terminating everything in the job before the update, or not firing the stealth update while non-PTY processes are still in the job, would prevent this. On a 0x80070020 launch failure, naming the PID that pins the old container would also be far more useful than the generic "another program is using this file" dialog.