Summary
On Linux desktops where XDG_CURRENT_DESKTOP is not one of the values Chromium recognises for password-store auto-detection (GNOME, KDE, Unity, MATE, Cinnamon, etc.), Chromium picks the basic_text (plaintext) backend for safeStorage. EncryptedAppSecretsStore.assertSecureStorageAvailable() correctly refuses to use basic_text and throws Secure secret storage is unavailable on this system.
Result: every feature that goes through EncryptedAppSecretsStore fails on these systems — even when a fully working Secret Service is running on D-Bus.
Affected setups include Hyprland (Omarchy ships it), Sway, i3, dwm, and any other window manager / compositor that doesn't advertise itself with one of the recognised XDG identifiers. This is a growing slice of the Linux user base.
User-visible impact
Each of the following is broken on affected systems:
- Sign-in to the Emdash account (
auth.emdash.sh) — Account sign-in failed: Error: Secure secret storage is unavailable on this system.
- GitHub connection — even when
gh auth is valid, the token can't be cached. getStatus() re-extracts the CLI token on every poll and the failed setSecret floods the log with Failed to cache CLI token in secure storage.
- Linear and Plain integrations —
setSecret fails on save.
- SSH password / passphrase storage for the remote-development feature.
Repro
- Run Emdash on a host with
XDG_CURRENT_DESKTOP=Hyprland (or sway, i3, dwm, ...). Any modern Arch / Omarchy install fits.
- The host has a perfectly functional Secret Service:
gnome-keyring-daemon --components=pkcs11,secrets is up, org.freedesktop.secrets is owned on the session bus, and secret-tool store / secret-tool lookup round-trips a value successfully.
- Launch Emdash. Watch stderr — the
Failed to cache CLI token in secure storage stack appears within seconds, repeated for every connection check tick.
- Try Settings → Sign in. Sign-in completes the OAuth flow but the final
accountCredentialStore.set throws and the session token never persists.
Root cause
Chromium picks the Linux password store from XDG_CURRENT_DESKTOP (os_crypt source). Anything not on its list maps to basic_text. Emdash inherits that default because nothing in src/main/index.ts calls app.commandLine.appendSwitch('password-store', ...). EncryptedAppSecretsStore.assertSecureStorageAvailable() (src/main/core/secrets/encrypted-app-secrets-store.ts:51) then refuses (correctly — you don't want tokens stored in plaintext) and throws.
In other words: the code is doing the right thing, the OS has a working keyring, Chromium's default is just too narrow.
Proposed fix
Add a small Linux-only block in src/main/index.ts, before app.whenReady(), alongside the existing app.commandLine.appendSwitch('ozone-platform-hint', 'auto') call. Roughly:
if (process.platform === 'linux' && !process.argv.some((a) => a.startsWith('--password-store='))) {
// Chromium's XDG_CURRENT_DESKTOP-based auto-detection doesn't know about
// Hyprland / sway / i3 / dwm / etc. and falls back to `basic_text`, which
// EncryptedAppSecretsStore refuses to use. Pick gnome-libsecret if a Secret
// Service is owning the bus name; otherwise leave the default and let the
// existing assertSecureStorageAvailable() error explain the problem.
if (await secretServiceAvailable()) {
app.commandLine.appendSwitch('password-store', 'gnome-libsecret');
}
}
Where secretServiceAvailable() does a lightweight check (e.g. dbus-send --session --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.NameHasOwner string:org.freedesktop.secrets, or any libdbus binding already pulled in transitively).
For KDE specifically, Chromium already auto-detects kwallet5 / kwallet6 correctly, so we only need the libsecret fallback.
If you'd rather avoid auto-detection entirely, a smaller fix is just to document the --password-store=gnome-libsecret workaround in the README's Linux install section and improve the thrown error to point users at it.
Workaround for users hitting this today
Exec=/path/to/emdash --no-sandbox --password-store=gnome-libsecret %U
(replace gnome-libsecret with kwallet5 / kwallet6 if you use KWallet instead). Once that's set, safeStorage switches to libsecret and all five credential paths work on Hyprland / sway / i3 / dwm.
Happy to send a PR if the auto-detection approach sounds reasonable.
Summary
On Linux desktops where
XDG_CURRENT_DESKTOPis not one of the values Chromium recognises for password-store auto-detection (GNOME,KDE,Unity,MATE,Cinnamon, etc.), Chromium picks thebasic_text(plaintext) backend forsafeStorage.EncryptedAppSecretsStore.assertSecureStorageAvailable()correctly refuses to usebasic_textand throwsSecure secret storage is unavailable on this system.Result: every feature that goes through
EncryptedAppSecretsStorefails on these systems — even when a fully working Secret Service is running on D-Bus.Affected setups include Hyprland (Omarchy ships it), Sway, i3, dwm, and any other window manager / compositor that doesn't advertise itself with one of the recognised XDG identifiers. This is a growing slice of the Linux user base.
User-visible impact
Each of the following is broken on affected systems:
auth.emdash.sh) —Account sign-in failed: Error: Secure secret storage is unavailable on this system.gh authis valid, the token can't be cached.getStatus()re-extracts the CLI token on every poll and the failedsetSecretfloods the log withFailed to cache CLI token in secure storage.setSecretfails on save.Repro
XDG_CURRENT_DESKTOP=Hyprland(orsway,i3,dwm, ...). Any modern Arch / Omarchy install fits.gnome-keyring-daemon --components=pkcs11,secretsis up,org.freedesktop.secretsis owned on the session bus, andsecret-tool store/secret-tool lookupround-trips a value successfully.Failed to cache CLI token in secure storagestack appears within seconds, repeated for every connection check tick.accountCredentialStore.setthrows and the session token never persists.Root cause
Chromium picks the Linux password store from
XDG_CURRENT_DESKTOP(os_crypt source). Anything not on its list maps tobasic_text. Emdash inherits that default because nothing insrc/main/index.tscallsapp.commandLine.appendSwitch('password-store', ...).EncryptedAppSecretsStore.assertSecureStorageAvailable()(src/main/core/secrets/encrypted-app-secrets-store.ts:51) then refuses (correctly — you don't want tokens stored in plaintext) and throws.In other words: the code is doing the right thing, the OS has a working keyring, Chromium's default is just too narrow.
Proposed fix
Add a small Linux-only block in
src/main/index.ts, beforeapp.whenReady(), alongside the existingapp.commandLine.appendSwitch('ozone-platform-hint', 'auto')call. Roughly:Where
secretServiceAvailable()does a lightweight check (e.g.dbus-send --session --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.NameHasOwner string:org.freedesktop.secrets, or any libdbus binding already pulled in transitively).For KDE specifically, Chromium already auto-detects
kwallet5/kwallet6correctly, so we only need the libsecret fallback.If you'd rather avoid auto-detection entirely, a smaller fix is just to document the
--password-store=gnome-libsecretworkaround in the README's Linux install section and improve the thrown error to point users at it.Workaround for users hitting this today
(replace
gnome-libsecretwithkwallet5/kwallet6if you use KWallet instead). Once that's set,safeStorageswitches to libsecret and all five credential paths work on Hyprland / sway / i3 / dwm.Happy to send a PR if the auto-detection approach sounds reasonable.