Windows Desktop MSIX: updater force-registers at quit into a live AppX container — app unlaunchable (0x80070020) until sign-out
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)
- 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.
- 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
- 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.
- 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, familyOpenAI.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
- 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).
- 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.
- 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.
- Minor, same subsystem:
CoworkVMServicelogs 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".
- 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, appmain.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.*
5 Comments
Same failure on a different machine — Windows 11 Pro 10.0.26200 x64, package family
Claude_pzs8sxrjxfjjc, update1.34493.1.0→1.37937.1.0on 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:
36-second round trip, update invisible, nothing wedged.
The failing runs end mid-update and never resume.
main.logends permanently here:The previous log file ends identically at
2026-08-11 00:11:29, withbeforeQuitForUpdate handler fired, going down for updatelogged 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
OpenProcessagainst a LocalSystem process fails from a medium-integrity token. My first sweeps were unelevated and reported "no packaged processes" whilecowork-svc.exewas running with package identity. Everything below is elevated.GetPackageFullNamesweep over every process:cowork-svc.exe→Claude_1.37937.1.0_x64__pzs8sxrjxfjjc), 8 Claude hives mounted,User.datlocked.CoworkVMService→ 0 packaged Claude processes machine-wide, 5 hives still mounted,User.datstill locked, unchanged after a further 15 s.Sysinternals
handle64(elevated) reportsNo matching handles foundfor all three of the package family path, the silo name, and theWindowsApps\Claudedirectory.The hives that survive with no members are the user ones:
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:
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| bothS_OK, nothing changed || Stop
CoworkVMService| releases only the service's own silos, not the user ones || Kill
chrome-native-host.exe(running from the packageLocalCache) | 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.logsimply 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:
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.
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
GetPackageFullNameat block time and logs what it finds).Incident 4 on this machine (2026-08-26, 1.37937.1 → 1.37937.3)
Two data points that extend the zero-member finding:
440B7F1Awas created during the previous (successful) update cycle on 08-25 and servedseveral app sessions before becoming the blocker. So the orphaning precedes the failing
update — the failing cycle just collides with it.
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 forcingRegisterByPackageFamilyName + ForceApplicationShutdownOptioninbeforeQuitForUpdate. Aleaked 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.
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 asClaude 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):
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:It attempts
PreserveApplicationDataand falls back to destructive removal when Windowsrefuses 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:
progress UI, rather than only in a log line the user never sees.
PreserveApplicationDatacan 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.exedoes fix the wedge variant, but back up
%APPDATA%\Claudeand%LOCALAPPDATA%\Claudefirst 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.
The container-leak variant _is_ recoverable from user mode — no sign-out. Fifth occurrence on my machine today (08-27,
1.37937.1.0→1.37937.3.0, same signature: zero packaged processes, 215/2080x80070020on 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(RtlAdjustPrivilege17 and 18 — both reported previously disabled, so they do need enabling explicitly), thenNtUnloadKey2(&oa, REG_FORCE_UNLOAD)against each hive of the app-side silo:All five returned
STATUS_SUCCESS. No bugcheck, no observable instability. After the first unloadUser.datwas openable again; the hive list confirmed all five gone.Launch immediately afterwards:
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 runningCoworkVMService, 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.datis 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 — andUser.datandUserClasses.datare 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
NtUnloadKey2withREG_FORCE_UNLOADagainst 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".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:--headlessmeans no window is ever created. Butgit.exe, spawned by the same process, gets a plain console host:Same for children of the bundled Claude Code CLI:
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: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 statuson 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
gitand hook/CLI child processes the same way the Electron utility children are already spawned —CREATE_NO_WINDOW(or the existingconhost --headlesspath). No behavior change, no cadence change; the windows simply stop appearing.Notes for anyone else chasing this
powershell.exe -WindowStyle Hiddendoes 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.WScript.Shell.Run(cmd, 0, False)wrapper does not prevent it either — also verified, still a plain conhost.conhost.exeprocesses 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.__InstanceCreationEvent WITHIN 1subscription misses sub-second processes entirely — which is exactly what these are.