[BUG] Desktop app: elevated-auth re-login navigates to /new and destroys the prompt being typed
Summary
In the Claude desktop app, an unsaved prompt in the composer is destroyed when the app triggers a re-login. The user types a long prompt, the app then demands a login ("second login today"), and after logging back in the composer is empty and the conversation is gone. No draft, no undo, no recovery.
Log analysis shows this is not a plain session expiry. The base session key is still valid; the app fails a background OAuth scope expansion and reacts by doing a full top-level navigation of the main window to the login page with returnTo=/new. The /new is the actual data-loss bug: even a perfect login returns the user to a new, empty conversation instead of the one they were composing in.
Root cause (from ~/.config/Claude/logs/main.log)
Sequence, all within one second, during background startup sync (plugins / skills / CCD download) — i.e. triggered by the app, not by any user action:
15:42:14 [error] OAuth token refresh failed: status=400, response={"error": "invalid_grant", "error_description": "Refresh token expired"}
15:42:14 [info] [oauth-v2] refresh rejected (auth_error) for orgId=<redacted>; tombstoning and falling through
15:42:14 [info] [oauth-v2] performing fresh /authorize for orgId=<redacted>
15:42:15 [info] oauth failed: authorize returned 403
{"type":"error","error":{"type":"permission_error",
"message":"Session is not fresh enough to grant elevated access. Sign in again to continue.",
"details":{"error_code":"session_stale_relogin"}},
"request_id":"req_011CeHtEBsrjPEPfaS42Hg8R"}
15:42:15 [info] oauth authorize rejected with session_stale_relogin; sessionKey is valid but too old for the requested scope expansion
15:42:15 [warn] Cannot get base query config: oauth failed (session_stale_relogin)
The main window is then navigated to:
https://claude.ai/login?returnTo=%2Fnew%3Freason%3Delevated_auth%26auth_kind%3Dsession_stale_relogin%26org_uuid%3D<redacted>&reauth=1&from=logout
Three separate problems in that one URL:
returnTo=/new— the return target is a brand-new conversation, so the composer is remounted empty. The conversation the user was actually in is not preserved.from=logout— the app treats a scope-expansion failure as a logout, even though the log explicitly sayssessionKey is valid. A still-valid session should not tear down the UI.- Top-level navigation of the main window instead of a popup/modal, which unmounts the composer and takes the typed text with it.
Recovery is logged 2m22s later (15:44:39 [info] [oauth] clearing latched session_stale_relogin failures) — so the user pays with their draft for a failure the app resolves on its own.
Steps to reproduce
- Log in to the desktop app; leave it running long enough for the OAuth refresh token to expire.
- Start typing a long prompt in an existing conversation.
- Let the app perform a background scope expansion (startup sync, plugin/skill sync, Cowork feature that needs elevated scope).
/authorizereturns 403session_stale_relogin; the login screen appears.- Log in. Observe: new empty conversation, typed prompt gone.
Expected behaviour
- The composer draft survives. Persist it per conversation to local app state on every change and rehydrate after any auth or navigation event (also across app restarts). This alone removes the data loss regardless of the auth work below.
returnTocarries the current conversation URL, not/new.- Elevated auth is not a logout. When the session key is valid and only the scope expansion is stale, re-auth belongs in a separate popup window or modal that leaves the main view mounted;
from=logoutis wrong here. - Refresh proactively. Refresh the OAuth token before it expires rather than discovering
invalid_grantmid-session, and back off background scope expansions while the user is actively typing. - Fail soft. If a draft is ever about to be dropped, surface a "restore draft" banner instead of discarding it silently.
Impact
Direct loss of user work, caused entirely by a background auth operation the user did not initiate and cannot see coming. Long, carefully composed prompts are the highest-value input the product receives. The practical user response is to draft in an external editor, which defeats the purpose of the app.
Environment
- Claude desktop app 1.34493.1 (
.deb,dpkgpackageclaude-desktop1.34493.1, amd64) - Electron 42.9.2, Node 24.18.1, Chromium renderer
- Ubuntu 26.04 LTS (Resolute Raccoon), kernel 7.0.0-30-generic, x86_64
- Occurred 2026-08-22 15:42 CEST; second forced login of the day
- Failing request id:
req_011CeHtEBsrjPEPfaS42Hg8R