Windows Desktop MSIX: updater force-registers at quit into a live AppX container — app unlaunchable (0x80070020) until sign-out

Status Open
Maintainer reply None cached
Activity 5 comments · opened Aug 26, 2026
Note: this concerns the Claude Desktop app for Windows (MSIX), not the CLI — filing here as the available public channel; happy to re-route if there is a better one. The bundled Claude Code CLI is unaffected (verified: it carries no package identity).

Product: Claude Desktop for Windows (sideloaded MSIX, package family Claude_pzs8sxrjxfjjc, SignatureKind: Developer)
Environment: Windows 11 Pro, OS build 10.0.26200, x64
Versions involved: 1.28929.0 → 1.32885.1 → 1.34493.1 → 1.37937.0
Frequency: 3 of 3 self-update cycles on this machine (2026-08-19, 2026-08-19→21, 2026-08-25)
Impact: 21 min, multi-hour (needed outside tooling), and 87 min of total app unavailability; the failure mode is silent (no dialog, no error, clicking the icon does nothing)

---

Summary

After the in-app updater applies an update, the app cannot launch. Every activation fails at
the AppModel layer with 0x80070020 (ERROR_SHARING_VIOLATION):

Microsoft-Windows-AppModel-Runtime/Admin
id=215  0x80070020: Cannot create the Desktop AppX container for package
        Claude_<new>_x64__pzs8sxrjxfjjc because an error was encountered converting the job.
id=208  0x80070020: Cannot create the process for package Claude_<new>... [LaunchProcess]

while the shell simultaneously reports success:

Microsoft-Windows-TWinUI/Operational id=1621
Activation of app Claude_pzs8sxrjxfjjc!Claude attempted. Execution state: ...
The operation completed successfully.

So the user sees nothing at all. Re-registering, RepairAppRegistration, and reinstalling the
MSIX all succeed as operations and change nothing (measured repeatedly across all three
incidents). The only recovery is ending the user session (sign out / reboot).

Root cause chain (from AppXDeploymentServer + AppModel-Runtime logs and app main.log)

  1. The app's Desktop AppX container outlives app exits and is reused across launches.

Measured: container {BF8B05AB-9DA6-11F1-B6AF-40D13333441E} was created 2026-08-21
14:36:17 and received the app's main process again on 08-22 (×2), 08-23, 08-24, and 08-25
(07:33 and 11:06) — six separate launches, each matching a Starting app line in
main.log, with the container surviving every exit in between. A container bound to the
old package version is therefore effectively always alive at update time.

  1. The updater stages the download correctly and even uses deferral on the Add:

``
08-25 11:06:12 Deployment Add ... Claude-61bfd4a1...msix
Options: NormalPriorityRequest, DeferRegistrationWhenPackagesAreInUse
``

  1. But at quit, the updater forces registration immediately instead of letting that

deferral resolve:

``
main.log 14:49:07 beforeQuitForUpdate handler fired, going down for update
AppXDeploy 14:49:07 RegisterByPackageFamilyName Claude_pzs8sxrjxfjjc
Options: ForceApplicationShutdownOption
AppXDeploy 14:49:11 Register 1.34493.1.0 → 1.37937.0.0 completed (4328 ms)
``

Registration lands ~4 s after the quit began. In incident 1, main.log's last
process-memory sample shows 10 Electron processes still alive 21 s after
beforeQuitForUpdate
— shutdown of the process tree demonstrably takes longer than the
updater waits.

  1. The old-version container survives the forced registration (incident 3: BF8B05AB

destroyed only at 15:41, by the user's reboot). Every launch of the new version then fails
converting the job (events 215/208 above) until the session ends.

Proof this is the updater's behavior, not a Windows/MSIX inevitability

A Store-signed MSIX app on the same machine (OpenAI Codex, family
OpenAI.Codex_2p2nqsd0c76g0) updates through the Store machinery and never hits this,
because its registrations defer instead of forcing:

08-23 00:00:10  Register OpenAI.Codex_26.818.5229 ...
                Options: RegisterHighestVersion, FailIfNeedsRemediation, LowPriorityRequest
                Result: Failed to reach state ResolvedDeferredRegistrations   ← stays staged
08-23 08:23:12  (retried — deferred again)
08-23 22:57:33  (retried — deferred again)
08-24 21:36:48  Register completed, 672 ms                                    ← quiescent

One Codex update that never found a quiet window was rolled back (DeStage,
08-21 22:14:51) rather than forced. Deferral → retry → rollback: registration never happens
while the old container is live, so relaunches never break.

Secondary defect observed (incident 2, 2026-08-19→21)

The updater trusts its own staged marker over Windows' registered state. With 1.32885.1
registered, the updater logged Staged version 1.34493.1 is still current for hours while
its temp MSIX files were zero bytes and deployment had aborted with:

0x80073D02: Unable to install because the following apps need to be closed
            Claude_pzs8sxrjxfjjc!Claude

It never re-verified against Get-AppxPackage, never re-downloaded, and never surfaced
anything. Recovery required manually downloading the MSIX (Authenticode-valid,
SHA-256 AD5EAD59...A55C), Add-AppxPackage -ForceApplicationShutdown, and a reboot.

Suggested fixes, in order of leverage

  1. Stop forcing registration in beforeQuitForUpdate. The Add already uses

DeferRegistrationWhenPackagesAreInUse; letting that deferral resolve (as the Store does)
removes the failure class outright. If forcing is retained, gate it on verified
quiescence: all package-identity processes exited and no live Desktop AppX container
for the family (AppModel-Runtime events 210/217 replay, or equivalent API).

  1. Reconcile updater state against the registered package version each cycle instead of

the self-maintained "staged" marker; treat a zero-byte/absent staged package as
not-staged; surface 0x80073D02 instead of looping silently.

  1. Post-update launch health check: after a self-update, if activation produces

AppModel-Runtime event 208 with 0x80070020, show a real dialog ("sign out to finish
updating") — currently the user gets absolute silence while the shell logs success.

  1. Minor, same subsystem: CoworkVMService logs on every start that it cannot configure or

disarm its SCM recovery actions (open service: Access is denied, running as
LocalSystem), warning that it "may be auto-restarted during package servicing".

  1. Diagnostic friction worth knowing: the log directory moved (~1.34493) from

%APPDATA%\Claude\logs to %LOCALAPPDATA%\Claude\Logs; the abandoned copy keeps its last
contents and looks like logging stopped.

Open question our instrumentation will answer

Whether the surviving container is held by unnoticed package-identity processes (child
processes the quit didn't reap) or is an AppModel bookkeeping leak with zero members. A local
watchdog now enumerates all processes via GetPackageFamilyName at block time, logs them,
kills them, and records whether the container dies. Happy to share that data from the next
occurrence.

Available on request

Full exports: Microsoft-Windows-AppXDeploymentServer/Operational,
Microsoft-Windows-AppModel-Runtime/Admin, Microsoft-Windows-TWinUI/Operational, app
main.log excerpts for all three incidents, the Codex-contrast excerpts above, a Crashpad
dump from a coincident (separate) GPU crash in incident 2, and the local watchdog scripts.

*Report prepared 2026-08-25 from Windows event logs and application logs on the affected
machine; all quoted lines are verbatim.*

View original on GitHub ↗

5 Comments

avish999 · 4 days ago

Same failure on a different machine — Windows 11 Pro 10.0.26200 x64, package family Claude_pzs8sxrjxfjjc, update 1.34493.1.01.37937.1.0 on 2026-08-26. Four occurrences here (08-05, 08-11, 08-21, 08-26).

Adding evidence that narrows it, including one point that contradicts the "surviving child process pins the container" diagnosis in #89680 and #89599.

1. There is a clean control case — this is a race, not a deterministic failure

Same code path, same machine, succeeded on 08-19:

2026-08-19 05:06:36 [info] [stealth-relaunch] Saved z-order anchor: 0x40cc8 (above our 0xd0e3a)
2026-08-19 05:06:36 [info] [stealth-relaunch] Saved navigation history (50 entries, active=49)
2026-08-19 05:06:36 [info] beforeQuitForUpdate handler fired, going down for update
2026-08-19 05:07:12 [info] [stealth-relaunch] Detected via marker file (windowVisible=true, windowMinimized=false, otherAppFullScreen=false)
2026-08-19 05:07:12 [info] [stealth-relaunch] Loaded navigation history (50 entries, active=49)
2026-08-19 05:07:12 [info] [stealth-relaunch] Restored z-order: our 0xaa16d6 behind anchor 0x40cc8

36-second round trip, update invisible, nothing wedged.

The failing runs end mid-update and never resume. main.log ends permanently here:

2026-08-21 03:17:51 [info] [stealth-update] Triggering stealth update after idle timeout
2026-08-21 03:17:51 [info] [stealth-relaunch] Saved z-order anchor: 0x34409cc (above our 0xaa16d6)
2026-08-21 03:17:51 [info] [stealth-relaunch] Saved navigation history (50 entries, active=49)
2026-08-21 03:17:52 [info] beforeQuitForUpdate handler fired, going down for update
2026-08-21 03:17:52 [info] Windows session ending (close-app) - quitting the app
<end of file — no relaunch line ever follows>

The previous log file ends identically at 2026-08-11 00:11:29, with beforeQuitForUpdate handler fired, going down for update logged twice — which looks like one retry that also failed.

2. On this machine the orphaned container has ZERO members

This is the part that does not match the "a surviving child process holds it" reading.

Methodological warning first: process-identity and handle sweeps run unelevated return confident false negatives, because OpenProcess against a LocalSystem process fails from a medium-integrity token. My first sweeps were unelevated and reported "no packaged processes" while cowork-svc.exe was running with package identity. Everything below is elevated.

GetPackageFullName sweep over every process:

  • Service running → 1 packaged process (cowork-svc.exeClaude_1.37937.1.0_x64__pzs8sxrjxfjjc), 8 Claude hives mounted, User.dat locked.
  • After stopping CoworkVMService0 packaged Claude processes machine-wide, 5 hives still mounted, User.dat still locked, unchanged after a further 15 s.

Sysinternals handle64 (elevated) reports No matching handles found for all three of the package family path, the silo name, and the WindowsApps\Claude directory.

The hives that survive with no members are the user ones:

\REGISTRY\WC\Silo<guid>user_sid      -> ...\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat
\REGISTRY\WC\Silo<guid>user_classes  -> ...\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\UserClasses.dat
\REGISTRY\WC\Silo<guid>software, \REGISTRY\WC\Silo<guid>com

So on this machine, killing processes cannot be the general fix — there is nothing left to kill.

3. Container creation succeeds for the packaged service and fails only for the app

Which pins the blocking resource to those two user hives:

09:58:39 [210] Created Desktop AppX container {...} for package Claude_1.37937.1.0_x64__pzs8sxrjxfjjc.
09:58:39 [211] Added process <pid> to Desktop AppX container ...
09:58:39 [215] 0x80070020: Cannot create the Desktop AppX container ... because an error was encountered converting the job.
09:58:39 [217] Destroyed Desktop AppX container {...}
09:58:39 [208] 0x80070020: Cannot create the process ... while configuring runtime. [LaunchProcess]

4. Recovery levers that do NOT work

All tried this session; none changed the hive count or released the lock:

| Lever | Result |
|---|---|
| IPackageDebugSettings::TerminateAllProcesses | S_OK; GetPackageExecutionState = Unknown before and after |
| IPackageDebugSettings::StartServicing + StopServicing | both S_OK, nothing changed |
| Stop CoworkVMService | releases only the service's own silos, not the user ones |
| Kill chrome-native-host.exe (running from the package LocalCache) | no effect |
| IApplicationActivationManager::ActivateApplication | hangs ~2 min, then the 215/208/217 loop above |

Only ending the user session clears it, consistent with this issue's conclusion.

5. Suggested fix

Matching the evidence: the stealth relaunch should retry activation with backoff until the vacated container has been torn down, instead of giving up after at most one attempt. The 08-19 case shows the identical path succeeding when ~36 s elapse.

Secondary ask: log the failed relaunch. At present main.log simply ends, so from the user's side the app silently dies with no dialog, no error, and no trace — clicking the icon does nothing, indefinitely.

6. Possibly unrelated, noticed while investigating

The packaged service logs this at every start and stop:

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.
Claude VM Service: failed to disarm SCM recovery actions for this stop; if the stop overruns, the service may be auto-restarted during package servicing: open service: Access is denied.

The service appears unable to manage its own SCM configuration from inside the package container. It did not turn out to be the cause here, but the "may be auto-restarted during package servicing" case it is trying to defend against is never actually being defended.

daringo42-creator · 3 days ago

Corroborating @avish999's analysis from the original machine, with data from an event-triggered watchdog that was armed after incident 3 (a Task Scheduler task on AppModel-Runtime event 208 that sweeps every process via GetPackageFullName at block time and logs what it finds).

Incident 4 on this machine (2026-08-26, 1.37937.1 → 1.37937.3)

23:00:25  beforeQuitForUpdate handler fired, going down for update
23:00:58  stealth-relaunch attempts begin failing: 0x80070020 (events 215/208)
23:01:03  watchdog fires (38 s after the block):
          BLOCKED: stale container 440B7F1A... for Claude_1.37937.1.0, created 08/25 19:28:24
          No user-visible package-identity processes, yet container survives
23:39:29  container destroyed — by session teardown (user reboot), nothing else
23:44:48  clean start on 1.37937.3

Two data points that extend the zero-member finding:

  1. The blocking container was 28 hours old and had already survived multiple app exits.

440B7F1A was created during the previous (successful) update cycle on 08-25 and served
several app sessions before becoming the blocker. So the orphaning precedes the failing
update — the failing cycle just collides with it.

  1. Nothing to kill, again. The sweep at block time found zero package-identity processes

visible to the user token (caveat adopted from your methodological warning: SYSTEM
processes are invisible unelevated — but the packaged service was demonstrably healthy in
its own separate container, which registered and ran the new version fine throughout).

Race variant vs leak variant — retry alone won't cover both

Your 08-19 success (36 s) and this machine's clean 08-25 cycle (~40 s, fresh post-reboot
containers) look like a race: teardown lags, a retry wins. Retry-with-backoff fixes that.

But the leak variant measured here persisted 28 hours across multiple sessions before it
ever collided with an update, and 39 further minutes under repeated activation attempts. No
backoff horizon recovers that; only session end does. So the §5 suggestion (retry + log the
failed relaunch) is necessary but not sufficient.

The variant-proof fix is on the registration side: the updater already stages with
DeferRegistrationWhenPackagesAreInUse, then overrides its own deferral by forcing
RegisterByPackageFamilyName + ForceApplicationShutdownOption in beforeQuitForUpdate. A
leaked old-version container is harmless until the moment a new version is registered
against it. Letting the deferral resolve (as Store-managed packages on the same machine
observably do — details in the issue body) means neither variant ever strands the user.

Interim UX ask stands: when post-update activation hits 0x80070020, surface "sign out to
finish updating" instead of silence — the shell logs activation success (TWinUI 1621) while
the user gets nothing.

Exposure note: releases are shipping near-daily now (1.37937.0 and .1 on 08-25, .3 on 08-26),
so each machine rolls these dice most days. Full watchdog logs available on request.

daringo42-creator · 3 days ago

Scope note: this is not limited to enterprise/sideloaded deployments — MSIX is the default consumer channel on Windows, so every Windows desktop install is on the affected path.

Worth stating explicitly because the download page frames MSIX as an enterprise thing ("standard enterprise deployment with MSIX installers"), which makes it easy to read this issue as a niche-deployment problem. It isn't.

Evidence

I downloaded the current consumer installer from the public endpoint
https://claude.ai/api/desktop/win32/x64/setup/latest/redirect — served as Claude Setup.exe,
~6.8 MB, Authenticode Valid, CN="Anthropic, PBC" via DigiCert EV,
SHA-256 4CC344008589A5C1262BEEE8F9AE52BBD47EFEBB680636225413C72DF1CFF164.

It is not an app installer; it is a Go bootstrapper whose primary path is MSIX. From its
embedded strings (inference from the binary, not source):

%s/%s/msix/latest/redirect        Downloading MSIX to %s
Installing MSIX: %s               MSIX installation succeeded
MSIX installation failed: %v      Offering Squirrel fallback
User chose Squirrel fallback      User declined Squirrel fallback
--msix flag: forcing MSIX install

So Squirrel is only an interactive fallback offered after an MSIX install fails. A default
consumer install lands on MSIX, in a Desktop AppX container, exposed to exactly the
container-leak/forced-registration interaction described in this issue. That raises the blast
radius from "sideloaders and enterprises" to "the Windows install base."

Separate defect found in the same binary: the repair path destroys user data by default

While confirming the above I hit something that looks independently worth fixing. The
bootstrapper has wedged-package recovery (Removing wedged package: %s,
All Claude MSIX state removed, AddPackage failed even after removing wedged packages: %w),
which is genuinely useful for the 0x80073D02 / stale-staged-state variant. But:

WARNING: AddPackage failed over an existing install (%v); removing wedged package(s)
         and retrying — app data will not be preserved

Windows rejected PreserveApplicationData for %s (0x80073CFA, requires developer mode);
         retrying removal without it — app data will not be preserved

It attempts PreserveApplicationData and falls back to destructive removal when Windows
refuses it — and Windows refuses it unless Developer Mode is enabled, which is off by
default and off on the machine I checked. Net effect: for a default-configured consumer, the
sanctioned repair for a wedged install silently discards local app data (conversations,
MCP configuration, local session state).

Suggestions, both cheap:

  1. Surface a confirmation before the destructive branch, or state the consequence in the

progress UI, rather than only in a log line the user never sees.

  1. Consider whether PreserveApplicationData can be avoided as a hard dependency here — or,

failing that, detect Developer Mode up front and warn before starting.

Practical consequence for anyone reading this thread while stuck: running Claude Setup.exe
does fix the wedge variant, but back up %APPDATA%\Claude and %LOCALAPPDATA%\Claude
first
unless Developer Mode is on. It does not fix the container-leak variant — nothing
in user mode does; that one still needs a sign-out or reboot.

avish999 · 3 days ago

The container-leak variant _is_ recoverable from user mode — no sign-out. Fifth occurrence on my machine today (08-27, 1.37937.1.01.37937.3.0, same signature: zero packaged processes, 215/208 0x80070020 on every launch). Instead of signing out I force-unloaded the orphaned silo's registry hives, and the app started immediately.

What I ran

Elevated, holding SE_BACKUP + SE_RESTORE (RtlAdjustPrivilege 17 and 18 — both reported previously disabled, so they do need enabling explicitly), then NtUnloadKey2(&oa, REG_FORCE_UNLOAD) against each hive of the app-side silo:

\REGISTRY\WC\Silo00f69436-…user_sid       -> …\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\User.dat
\REGISTRY\WC\Silo00f69436-…user_classes   -> …\Packages\Claude_pzs8sxrjxfjjc\SystemAppData\Helium\UserClasses.dat
\REGISTRY\WC\Silo00f69436-…com
\REGISTRY\WC\Silo00f69436-…software
\REGISTRY\WC\Silo29a60a3c-…com

All five returned STATUS_SUCCESS. No bugcheck, no observable instability. After the first unload User.dat was openable again; the hive list confirmed all five gone.

Launch immediately afterwards:

11:54:33 [210] Created Desktop AppX container {E81041C5-…} for package Claude_1.37937.3.0_x64__pzs8sxrjxfjjc
11:54:33 [211] Added process 35548 to Desktop AppX container e81041c5-…
11:54:33 [201] Created process 35548 for application Claude_pzs8sxrjxfjjc!Claude

Zero 215, zero 208. Twelve processes, app fully usable, and the Windows session — every other app, every terminal — untouched.

Do not unload the silo group whose hives live under …\WindowsApps\Claude_<version>\Cache\. That one belongs to the running CoworkVMService, which as you note is healthy in its own container throughout. Only the group on the per-user package container path (…\Packages\Claude_pzs8sxrjxfjjc\…) is the orphan.

Two corrections to my earlier comment

1. The blocking resource is the hives, not the job. I read 0x80070020 "converting the job" as pointing at the container's job object. Clearing the hives alone was sufficient, so whatever state the job is in, the mounted hives are what the create actually trips on.

2. A locked User.dat is not diagnostic, and I should not have cited it as evidence. Control, on this machine, right now: the app is healthy with twelve packaged processes alive — and User.dat and UserClasses.dat are both locked against a shared-read open. Same for WhatsApp Desktop and Copilot with zero processes running. A mounted hive is always locked; the lock says nothing about whether the container is orphaned. What identifies the orphan is a silo group on the per-user container path with no live member processes — the zero-member finding in my §2 stands, the lock evidence around it does not.

Caveats

n=1, and NtUnloadKey2 with REG_FORCE_UNLOAD against a hive the kernel itself still references is formally unsupported — I would expect it to be capable of bugchecking; it simply did not here. This is a recovery lever for people who are currently stuck, not a fix, and it does nothing about recurrence: the next stealth update recreates the orphan.

It does, though, narrow the problem usefully. @daringo42-creator's 28-hour leaked container is real and no backoff horizon reaches it — but "only session end clears it" turns out to be false. A supported equivalent of this unload, invoked by the updater when its own relaunch hits 0x80070020, would recover the leak variant in-process. The registration-side fix you argue for is still the right one; this just means the interim UX ask can be better than "sign out to finish updating".

daringo42-creator · 2 days ago

Separate, much smaller Windows issue found while instrumenting the update failure — filing it here since it is the same "child processes spawned inconsistently" theme and the evidence came from the same investigation. Happy to move it to its own issue if preferred.

Symptom

Console windows flash on screen during ordinary use of Claude Desktop — brief, repeated, no pattern the user can predict. Enough to be reported unprompted as "terminal flashes every so often."

Cause: some child processes get a headless console, others do not

Claude Desktop already spawns its own Electron utility children correctly. Captured on Windows 11 Pro 26200, Claude 1.40609.0.0:

conhost.exe  --headless --width 80 --height 24 ...   <- Claude.exe --type=utility --utility-sub-type=node.mojom.NodeService

--headless means no window is ever created. But git.exe, spawned by the same process, gets a plain console host:

14:16:32  git.exe  parent=Claude.exe (WindowsApps\Claude_1.40609.0.0_x64__pzs8sxrjxfjjc\app\Claude.exe)
                   grandparent=explorer.exe
          -> conhost.exe (no --headless)

Same for children of the bundled Claude Code CLI:

conhost.exe (no --headless)  <- claude.exe (AppData\Roaming\Claude\claude-code\2.1.247\claude.exe --output-format stream-json ...)

Measured rate during normal use: 5 plain console hosts in 75 seconds in one window, 1 in the next — bursty, tracking session/git activity rather than a timer.

What drives the git ones

From main.log, two distinct callers:

[refreshSourceRef] refreshed origin/main in 1229ms { baseRepo: '...' }     ~ every 10 min
[gitDiff] fetchGitDiff timing: metadata=155ms diff=105ms total=260ms files=100+   on activity

To be clear, the polling cadence itself looks entirely reasonable — one fetch per ~10 minutes, measured at 1.25 s, which is network round-trip, not repo size (git status on that repo is 52 ms, 189 objects, 1.4 MiB). This is not a request to poll less. It is only that each invocation is visible.

Ask

Spawn git and hook/CLI child processes the same way the Electron utility children are already spawned — CREATE_NO_WINDOW (or the existing conhost --headless path). No behavior change, no cadence change; the windows simply stop appearing.

Notes for anyone else chasing this

  • powershell.exe -WindowStyle Hidden does not prevent it. The console host is created before PowerShell parses the flag. Verified: a scheduled task using that flag still yields a plain (non-headless) conhost.
  • A WScript.Shell.Run(cmd, 0, False) wrapper does not prevent it either — also verified, still a plain conhost.
  • Counting conhost.exe processes is a poor detector (it moves constantly on a busy machine). Diffing conhost PIDs and resolving the parent against a rolling PID→command-line map works unelevated and survives the parent exiting, which it usually does within ~100 ms.
  • Process-creation auditing (Security 4688) is not needed for this and was a dead end for us: the Security log requires elevation to read, and a WMI __InstanceCreationEvent WITHIN 1 subscription misses sub-second processes entirely — which is exactly what these are.