anthropics / anthropics/claude-code
[BUG] Windows/MSIX: stealth update relaunch fails with 0x80070020 — app dead 9h, then re-seeds a dead v1 OAuth grant and forces re-login
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
> All timestamps are **local, PDT (UTC−07:00)** — the desktop `main.log` is local time, not UTC.
> Related, not duplicates: #87950, #81512, #89812 (same terminal error, different trigger, all Linux/macOS), #91469 (same two config keys, different surface).
### Preflight Checklist
- [x] I have searched existing issues
- [x] This is a single bug report (two tightly-coupled defects in one causal chain; happy to split)
- [x] I am using the latest version of Claude Code
## Summary
An MSIX auto-update applied cleanly at 2026-09-03 23:11:10, but the **stealth relaunch failed** with `0x80070020` (`ERROR_SHARING_VIOLATION`) on both AppX container creation and `LaunchProcess`. The app did not come back. It stayed dead for 9h 44m while the machine was awake for the next four hours, leaving a Windows "already running" dialog on screen that I dismissed the following morning.
When I finally launched it by hand at 08:55:41, that start found the stale relaunch marker, seeded `oauth-v2` from the **legacy v1 token cache**, and hit a refresh token that had already died on 2026-09-02. The failed refresh escalated into a scope expansion, which the server correctly rejected as `403 session_stale_relogin` — forcing a full interactive re-login.
Two defects, one chain:
| | Defect | Severity |
|---|---|---|
| **A** | Stealth update relaunch fails with `0x80070020`; no retry, no recovery, app simply never restarts | App unusable until manually launched |
| **B** | Post-failure cold start seeds `oauth-v2` from a v1 grant whose refresh token already failed → forced re-login | Full interactive sign-in |
Defect A is the trigger. Defect B is why it costs a login rather than just a restart.
## Environment
| | |
|---|---|
| App | Claude Desktop `1.46388.1.0`, MSIX (`WindowsApps\Claude_1.46388.1.0_x64__pzs8sxrjxfjjc`) |
| Upgraded from | `1.44121.4.0` |
| Staged next | `1.46388.2.0` — **already deferred**, see "Armed to recur" |
| Claude Code | `2.1.260` |
| OS | Windows 11 Pro 10.0.26200, x64 |
| Electron/Node | Node `24.18.1` |
| Account | Personal Max 20x, Google SSO, `installMethod: native` |
| Config | `%APPDATA%\Claude\config.json` |
| Logs | `%LOCALAPPDATA%\Claude\Logs\main.log`; `Microsoft-Windows-AppModel-Runtime/Admin`; `Microsoft-Windows-AppXDeploymentServer/Operational` |
## Defect A — the relaunch never happened
The app quit itself cleanly for the update:
```
2026-09-03 23:11:07 [info] [stealth-update] Triggering stealth update after idle timeout
2026-09-03 23:11:07 [info] [stealth-relaunch] Saved z-order anchor: 0x20e19ea (above our 0x7f0580)
2026-09-03 23:11:07 [info] [stealth-relaunch] Saved navigation history (36 entries, active=35)
2026-09-03 23:11:07 [info] [CCD] Killing 7 PTY process tree(s) on quit
2026-09-03 23:11:08 [info] beforeQuitForUpdate handler fired, going down for update
2026-09-03 23:11:08 [info] Windows session ending (close-app) - quitting the app
```
MSIX registration succeeded:
```
23:11:08 [603] Started deployment RegisterByPackageFamilyName ... ForceApplicationShutdownOption
23:11:08 [855] updateList: Claude_1.44121.4.0 is updating to Claude_1.46388.1.0
23:11:10 [400] Deployment Register operation ... finished successfully.
23:11:10 [472] Moving package folder ...Claude_1.44121.4.0... to ...\WindowsApps\Deleted\...
```
Then the relaunch died — `Microsoft-Windows-AppModel-Runtime/Admin`:
```
23:11:10 [215] Error 0x80070020: Cannot create the Desktop AppX container for package
Claude_1.46388.1.0_x64__pzs8sxrjxfjjc because an error was encountered converting the job.
23:11:10 [215] Error 0x80070020: (repeated)
23:11:10 [208] Error 0x80070020: Cannot create the process for package
Claude_1.46388.1.0_x64__pzs8sxrjxfjjc because an error was encountered
while configuring runtime. [LaunchProcess]
```
`0x80070020` is `ERROR_SHARING_VIOLATION`. The old package's container was still alive — and stayed alive long past the update:
```
2026-09-04 00:54:47 [217] Destroyed Desktop AppX container {F68C6D20-...}
for package Claude_1.44121.4.0_x64__pzs8sxrjxfjjc
```
That is **1h 43m after** the update registered. `ForceApplicationShutdownOption` was requested and evidently did not fully take.
There is no retry. `main.log` has zero entries between `23:11:08` and `08:55:42`, and `AppModel-Runtime` records no further launch attempt until I started it by hand. Over the previous 45 days this `0x80070020` appears exactly once — 2026-09-03 23:11:10 — so this is a race, not a steady-state condition.
## Defect B — the eventual start re-seeds a dead OAuth grant
1. `config.json` holds two caches: `oauth:tokenCache` (v1, **4 entries**) and `oauth:tokenCacheV2` (v2).
2. The v2 cache loads **3 entries** on every startup — 2026-09-02 15:42:32, 2026-09-02 23:53:38, 2026-09-04 08:55:47. It never gains the fourth.
3. The absent fourth is the only grant carrying `user:sessions:claude_code` — the scope Claude Code requires. It exists **only** in v1, under the pre-migration bare-clientId key. The v2 cache holds the same client/org under `acct:|:…` but with the narrower set `user:inference user:file_upload user:profile`.
4. The manual start recognized the abandoned relaunch and then filled the gap from v1:
```
08:55:43 [stealth-relaunch] Loaded z-order anchor: 0x20e19ea
08:55:43 [event-loop-stall] update relaunch (stale marker) stands in for an unheard suspend
08:55:47 [oauth-v2] loaded token cache with 3 entries
08:55:47 [oauth-v2] seeded from v1 cache for orgId=
```
5. That seeded refresh token was already dead — it first failed **2026-09-02 15:28:13** with `{"error":"invalid_grant","error_description":"Refresh token expired"}`, and the v2 path never rewrites the v1 entry, so the dead grant persists in `config.json` indefinitely.
6. ```
08:55:49 [error] OAuth token refresh failed: status=400 ... invalid_grant
08:55:49 [oauth-v2] refresh rejected (auth_error); tombstoning and falling through
08:55:49 [oauth-v2] performing fresh /authorize
08:55:49 oauth failed: authorize returned 403 (permission_error, session_stale_relogin)
08:55:49 oauth authorize rejected with session_stale_relogin;
sessionKey is valid but too old for the requested scope expansion
08:55:49 [sessions-bridge] Cowork OAuth stale-session; parking bridge until re-login
```
7. Full sign-in; `sessionKey rotated` at 09:00:09.
**The 403 is a consequence, not the cause.** The server is handed a dead grant and then asked for elevated scope by a session that is legitimately not fresh.
## Timeline
| Time (PDT) | Source | Event |
|---|---|---|
| 2026-09-03 21:53:36 | main.log | `[updater] Found an update, downloading` |
| 2026-09-03 21:54:01 | main.log | `Update downloaded and ready to install { releaseName: 'Claude 1.46388.1' }` |
| 2026-09-03 23:11:07 | main.log | Idle timeout → `[stealth-update] Triggering stealth update`; z-order + 36 nav entries saved |
| 2026-09-03 23:11:08 | main.log | `beforeQuitForUpdate` → app quits |
| 2026-09-03 23:11:10 | AppXDeployment | MSIX Register succeeds; old package folder moved to `Deleted` |
| **2026-09-03 23:11:10** | **AppModel-Runtime** | **`0x80070020` ×3 — container + `LaunchProcess` fail. App is now dead.** |
| 2026-09-04 00:54:47 | AppModel-Runtime | Old `1.44121.4.0` AppX container finally destroyed (1h 43m late) |
| 2026-09-04 03:11:11 | Kernel-Power | System enters sleep (nothing to relaunch) |
| 2026-09-04 07:09:51 | Power-Troubleshooter | Wake, source: Power Button. Stale "already running" dialog dismissed |
| 2026-09-04 08:55:41 | AppModel-Runtime | Manual launch: `Created process 75404 for Claude_pzs8sxrjxfjjc!Claude` |
| 2026-09-04 08:55:43 | main.log | `update relaunch (stale marker) stands in for an unheard suspend` |
| 2026-09-04 08:55:47 | main.log | `[oauth-v2] seeded from v1 cache` |
| 2026-09-04 08:55:49 | main.log | `invalid_grant` → `403 session_stale_relogin` |
| 2026-09-04 09:00:09 | main.log | `sessionKey rotated` — **forced re-login** |
App unavailable: **9h 44m** (23:11:10 → 08:55:41). Time from manual launch to completed re-login: 4m 29s.
The previous forced re-login was 2026-09-02 15:47:17, giving a 41h 27m interval — which is what made this look like a ~48h session TTL. It is not. The CLI's own refresh token is valid 28 days (issued 2026-09-04 09:01:26, expires 2026-10-02 03:37).
## Armed to recur
`1.46388.2` downloaded at 08:56:46 and is already blocked behind the same condition:
```
08:56:44 [638] Packages were not updated because affected apps are still running.
Running apps: {Claude_pzs8sxrjxfjjc!Claude}
08:56:44 [658] Warning Marking package {Claude_1.46388.2.0} for deferred registration
because {Claude_1.46388.1.0} is still running.
```
The next idle-timeout stealth update will run the identical sequence against a live process tree.
## What should happen
1. **Retry the relaunch.** A `0x80070020` on `LaunchProcess` after a successful package Register should back off and retry, not abandon the app. Nine hours of downtime from a single transient sharing violation is the core harm.
2. **Verify old-process teardown before registering.** `ForceApplicationShutdownOption` returned success while the old AppX container survived another 1h 43m. Wait on actual container destruction, or register with deferred registration and relaunch on next user activation.
3. **Surface the failure.** The user-visible artifact was a generic Windows "already running" dialog with no path to recovery. If the relaunch fails, the app should say so and offer a retry.
4. **Don't seed `oauth-v2` from a v1 entry whose refresh token already failed.** Tombstone the v1 entry on first `invalid_grant`, or validate before seeding.
5. **Don't let a failed silent refresh escalate to a scope expansion.** Re-requesting the scopes the session already holds would avoid the freshness check and recover without user interaction.
6. **Fix the v1→v2 migration.** The v2 cache being stuck at 3 of 4 entries across every startup is the underlying defect; anyone with a `user:sessions:claude_code` grant is exposed.
## Workaround
For defect B only, deleting the two keys clears the poisoned entry — per @VanDmitry's #91469, same artifact, different symptom (Claude in Chrome 401 loop):
```powershell
# Fully quit Claude Desktop first.
$cfg = "$env:APPDATA\Claude\config.json"
Copy-Item $cfg "$cfg.bak-$(Get-Date -Format yyyyMMddTHHmmss)"
# Remove ONLY: oauth:tokenCache and oauth:tokenCacheV2
```
Costs one immediate re-login. No workaround for defect A short of disabling auto-update.
## Relationship to existing reports
#87950, #81512 and #89812 report `session_stale_relogin` forced logouts every 24–48h and each explicitly rules out local credential storage, concluding the invalidation is server-side. On their evidence that is reasonable. This report is a **different trigger reaching the same server error**: those reporters state the app was never closed, whereas here the app *was* closed — by an update whose relaunch failed. The distinguishing log line is `[oauth-v2] seeded from v1 cache`, which returns zero results across the tracker.
Worth checking whether their logs show `invalid_grant` immediately preceding the 403. If so, the v1-seed path may be common to all four and the "server-side TTL" reading is wrong.
## Not affected
For triage completeness, everything else survived cleanly: Claude Code CLI credentials, Gmail and Google Calendar connectors, the remote-control device bridge, and the Cowork sessions bridge (parked 08:55:49, restarted 09:00:09). No connector authorization was lost. The only unrelated failure in this log is `plugin:pdf-viewer:pdf` failing to spawn under `npx` with `CONNECTION_CLOSED`.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start from the update path identified by the log tags `[stealth-update]` and `[stealth-relaunch]`, using `%LOCALAPPDATA%\Claude\Logs\main.log` plus AppModel-Runtime/AppXDeployment events to reproduce the 0x80070020 relaunch failure. Then trace the oauth-v2 startup path that logs `seeded from v1 cache`. Done means relaunch failure is retried or surfaced, and stale v1 grants no longer force re-login.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- electron, node.js
- Domain
- authentication, desktop, release
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100