[BUG] Desktop Linux on ChromeOS/Crostini: silent fallback to unencrypted storage — "sign-in won't be saved" despite working gnome-keyring
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
NB: I have been in the backseat for this thing. The issue is definitely real, and the solution has definitely been working as I've restarted Claude Desktop quite a few times. But it's been Claude Code fixing itself this entire time, including this bug report. I'm just the supervisor, I guess! I can't vouch for all of it... or understand it.
On ChromeOS Crostini, AKA Linux on ChromeOS, (official claude-desktop .deb, Debian 13 container), every launch shows:
⚠️ Your sign-in won't be saved on this device. Install and unlock a system keyring (such as GNOME Keyring), then restart the app.
The warning is misleading: GNOME Keyring is installed, running, and unlocked. The app's session token is stored unencrypted and sign-in does not persist.
Root cause: Chromium's os_crypt backend auto-detection picks the portal backend (org.freedesktop.impl.portal.Secret). On Crostini this D-Bus name is activatable but has no working implementation — neither xdg-desktop-portal-gtk nor ChromeOS's desktop.cros portal backend provides the Secret interface. Init fails (Local State records "os_crypt":{"portal":{"prev_init_success":false}}) and the app silently falls back to unencrypted storage instead of trying the functional gnome-libsecret backend. A contributing factor is that Crostini sets XDG_CURRENT_DESKTOP=X-Generic, which defeats desktop-environment-based detection.
Launching with claude-desktop --password-store=gnome-libsecret fully resolves it: a Claude Safe Storage item (schema chrome_libsecret_os_crypt_password_v2) appears in the default keyring collection, the warning stops, and sign-in survives app/VM restarts.
Given ChromeOS's Linux environment is a large population of Debian-based desktop Linux users, this likely affects most Chromebook installs of the official .deb. Independent report of the same issue + same fix: https://note.com/sho_ai_magic3975/n/n410966a4f543
What Should Happen?
When the portal Secret backend fails to initialize, the app should fall back to probing org.freedesktop.secrets directly (gnome-libsecret) before resorting to unencrypted storage — or the launcher/.desktop file should detect Crostini (e.g. SOMMELIER_VERSION env var, or XDG_CURRENT_DESKTOP=X-Generic combined with a functioning org.freedesktop.secrets) and pass --password-store=gnome-libsecret.
At minimum, the warning should be actionable: tell the user which backend was tried and that --password-store=gnome-libsecret exists. The current message sends users hunting for a keyring they already have installed and unlocked.
Error Messages/Logs
UI warning: "Your sign-in won't be saved on this device. Install and unlock a system keyring (such as GNOME Keyring), then restart the app."
~/.config/Claude/"Local State" after failed init:
{"os_crypt":{"portal":{"prev_desktop":"X-Generic","prev_init_success":false}},...}
Keyring is demonstrably healthy (daemon running, default collection unlocked):
$ gdbus call --session --dest org.freedesktop.secrets --object-path /org/freedesktop/secrets/collection/Default_5fKeyring --method org.freedesktop.DBus.Properties.Get string:org.freedesktop.Secret.Collection string:Locked
(<false>,)
After launching with --password-store=gnome-libsecret, the safe-storage key is created and persists:
Item label: 'Claude Safe Storage'
Attributes: {'application': 'Claude', 'xdg:schema': 'chrome_libsecret_os_crypt_password_v2'}
Steps to Reproduce
- On a Chromebook with Linux (Crostini) enabled — Debian 12/13 container — install the official package:
sudo apt install claude-desktop(from the downloads.claude.ai apt repo) - Ensure gnome-keyring is installed and unlocked (
sudo apt install gnome-keyring; default collection unlocked — verifiable via the gdbus call in the logs section) - Launch Claude Desktop (launcher icon or
claude-desktopin the terminal) and sign in - Observe the "Your sign-in won't be saved on this device" warning banner despite the working keyring; note
"prev_init_success":falseunderos_crypt.portalin~/.config/Claude/"Local State", and noClaude Safe Storageitem in the keyring - Quit, relaunch as
claude-desktop --password-store=gnome-libsecret - Warning is gone;
Claude Safe Storageitem now exists in the keyring; sign-in persists across app and VM restarts
Claude Model
Sonnet (default)
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
Claude Desktop 1.21459.0 (Electron 42.5.1), official .deb
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
Workaround for affected users until patched:
Override the desktop entry so launcher launches carry the flag:
cp /usr/share/applications/com.anthropic.Claude.desktop ~/.local/share/applications/
sed -i 's|^Exec=claude-desktop|Exec=env XDG_CURRENT_DESKTOP=GNOME claude-desktop --password-store=gnome-libsecret|' ~/.local/share/applications/com.anthropic.Claude.desktop
update-desktop-database ~/.local/share/applications
And for terminal launches, a wrapper in ~/.bashrc:
claude-desktop() {
command claude-desktop --password-store=gnome-libsecret "$@"
}
Environment: ChromeOS Crostini (termina VM / penguin container), Debian 13 trixie, Wayland via sommelier, XDG_CURRENT_DESKTOP=X-Generic, gnome-keyring 0.21.7 (libsecret 0.21.7).
(Related but separate ChromeOS-side issue, being reported to Google: on a cold Crostini VM start, the ChromeOS launcher's app-launch request is dropped by vm_cicerone — "Garcon channel not connected after 5 seconds" — and never retried, so the app doesn't open even once storage is fixed. Not an Anthropic bug; noted so Chromebook users finding this issue have the complete picture.)
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Same bug outside Crostini — reproduced on Sway/wlroots, which suggests the trigger is broader than ChromeOS.
Environment: claude-desktop 1.24012.9 (official apt), Debian 13 trixie amd64, kernel 7.1.3, sway 1.12 / wlroots 0.20,
XDG_CURRENT_DESKTOP=Sway, xdg-desktop-portal 1.20.3, xdp-gtk 1.15.3, xdp-gnome 48.0, gnome-keyring 48.0.Identical symptom and identical
Local Stateevidence:gnome-keyring running,
org.freedesktop.secretson the bus, default collection unlocked.The mechanism here is slightly different from
X-Genericdefeating DE detection. On a non-GNOME Wayland compositor, xdg-desktop-portal binds no Secret backend at all: onlygnome-keyring.portalandkwallet.portalimplementorg.freedesktop.impl.portal.Secret, andgnome-keyring.portaldeclaresUseIn=gnome. Withdefault=gtk;wlrinportals.confand no explicit Secret route, the interface has no implementation to route to.A second workaround, without
--password-store: add an explicit route to~/.config/xdg-desktop-portal/portals.confthen
systemctl --user restart xdg-desktop-portal.prev_init_successflips totrueand sign-in survives restarts.I A/B tested this to rule out the portal restart being the actual fix — both arms included an identical restart:
| portals.conf |
busctl --user introspect org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop \| grep -c org.freedesktop.portal.Secret||---|---|
| no Secret route | 0 |
|
org.freedesktop.impl.portal.Secret=gnome-keyring| 1 |So the explicit route is what binds the backend, and notably it does take effect despite the backend's own
UseIn=gnome.This supports the report's core request from another angle: the portal path can work here, so the silent fallback to unencrypted storage is the defect rather than the portal being genuinely unavailable.
Diagnostic note for anyone reproducing: don't introspect
org.freedesktop.impl.portal.Secretto check whether a backend is bound.gnome-keyring.portalsetsDBusName=org.freedesktop.secrets, so that bus name never exists and the call simply times out — which reads exactly like "still broken". Probe the frontend instead:Also worth adding to repro instructions: the app must be fully killed between attempts, not window-closed (see #78798 §3) — otherwise the surviving process absorbs the relaunch and neither new flags nor new portal config take effect. This produced several misleading test runs before I noticed.
Third environment (Hyprland), but the useful part is below: I went through the bundled
app.asarand found the exact branch where this decision is made.Environment: claude-desktop 1.24012.9 (Electron 42.7.0, Chrome 148.0.7778.280), Arch/Omarchy, Hyprland 0.56.0,
XDG_CURRENT_DESKTOP=Hyprland, gnome-keyring 50.0,org.freedesktop.secretson the bus, default collection unlocked. Installed from the AUR package, which extracts the official.debpayload verbatim and only adds Debian→Arch path symlinks for the Cowork VM stack (plus dropping the Debian-only lintian files) — so this is upstream's build, not a repackaging artifact.Same fingerprint as the two reports above:
--password-store=gnome-libsecretresolves it completely:Claude Safe Storageitem created, warning gone, sign-in persists.Why it happens, in the app's own code
The app doesn't rely solely on Chromium's detection — it has its own:
password-storeoccurs four times in the bundle — three in code, one inside a log string:desktop() !== "kde"→ early returndesktop() === "kde" && !commandLine.hasSwitch("password-store")→ probeorg.kde.kwalletd{5,6}over D-BusappendSwitch("password-store", "basic")— when kwalletd is reachable but has no walletWhich means:
|
desktop()| Behaviour ||---|---|
|
kde| Probed over D-Bus and handled ||
gnome| Detected — but the only use I can find for the value is picking a tray icon, never password-store. Works anyway, because Chromium recognisesGNOME. ||
other| No branch at all. Falls through to Chromium's failed-portal autodetect. |The app never calls
appendSwitch("password-store", "gnome-libsecret")anywhere — the only value it ever sets is"basic". Crostini (X-Generic), Sway and Hyprland all land inotherand inherit the fallback.The fix mirrors code that already exists
The KDE path already probes D-Bus for the absence of a wallet in order to downgrade safely. What's missing is the same probe for the presence of a Secret Service, in order to upgrade:
This is strictly safer than the KDE branch that already ships: it upgrades rather than downgrades, and only when the service actually answers on the bus.
Not a niche configuration
XDG_CURRENT_DESKTOP=Hyprlandisn't something the user sets. It comes fromDesktopNames=Hyprlandin/usr/share/wayland-sessions/hyprland.desktop, shipped by thehyprlandpackage itself — the same mechanism gives Sway its value. Every user of these compositors gets a string the app classifies asother, by default, with no way to influence it short of lying about their desktop (which breaks xdg-desktop-portal screen sharing).hyprlandis packaged in Debian (trixie-backports, forky, sid), so this reproduces on the official.debon a supported distribution.Secondary
The warning text should name the backend that was actually selected. "Install and unlock a system keyring (such as GNOME Keyring)" is actively misleading when a keyring is installed, running and unlocked — which is the case in all three reports on this issue. Something like "secure storage unavailable (backend: basic_text)" would at least be diagnosable.
Still present in 1.40609.0.
My environment: Void Linux, labwc/wlroots,
XDG_CURRENT_DESKTOP=labwc:wlroots→otherin thedesktop()branch @ocewers identified. claude-desktop installed from the official.debpackage. gnome-keyring running,org.freedesktop.secretson the bus, collection unlocked.Same fingerprint:
--password-store=gnome-libsecretfixes it.The warning never reaches the user
Fresh, empty
--user-data-dir.main.loghas the detection:but no
insecure-storage notice scheduledline and no toast. 125[info]lines in the same file, so the level isn't masking it; the profile was empty, so the persistedinsecureStorageNoticeShownflag doesn't explain it either.Even when it fires, a one-shot toast on a 5s timer — early-returning on a persisted flag, racing window readiness — is the wrong mechanism for a permanent condition that is re-detected at every startup.
Why it's hard to diagnose
The symptom isn't "sign-in won't be saved". The session cookie outlives the missing refresh token, so it presents as a forced magic-link login every 1–2 days, citing only security reasons — the session-expiry message. That reads as server-side policy, not local storage. Four days to find the Chromium line above, which needs
--enable-logging=stderr --v=1since child processes run with--disable-logging.Suggestion
Besides the D-Bus probe @ocewers proposed: make the insecure-storage state persistent rather than a one-shot toast, and have the re-login prompt name the cause when
isEncryptionAvailable()is false — that prompt is the one users actually see.