Files
hassio-addons/claude_desktop/SIGN_IN.md
2026-08-03 14:22:18 +00:00

11 KiB

Claude Desktop sign-in — shipped fixes and planned work

Two related sign-in problems when Claude Desktop runs inside the LinuxServer Selkies streamed desktop.

Status:

  • Shipped: Problem B (sign-in persistence) — the autostart bootstrap landed in v1.4, and the gnome-keyring package itself was added in v1.17. That approach was later reverted: v1.24-ish removed gnome-keyring again because it prompts for a keyring password on first boot, which blocked Claude Desktop from ever launching — but the launch flag was left forcing the now-daemonless libsecret backend, so safeStorage silently went unavailable again (recurring "sign in again", and the Claude app's dispatch tab showing the desktop as offline until a fresh sign-in was done from a computer). v1.35 switched to --password-store=basic plus a cont-init script that re-syncs the persistent openbox autostart from the image on every boot — but that flag alone does nothing, and the bug survived it untouched. Actually fixed in v1.37, which adds the application-side opt-in the basic backend requires; see "Why v1.35 did not work" below.
  • Planned only: Problem A (in-desktop browser for OAuth) is intentionally not implemented. The image ships no browser; complete the login with the user-side workaround below.

Problem A — Cannot complete login

Symptoms:

  • "Continue with Google" does nothing.
  • "Continue with email" sends a link that "must be opened from the same machine".

Root cause

Claude Desktop signs in through an external web page (Google OAuth or the email magic link) and then hands the token back to the app via the claude:// URL scheme (auto-registered on first launch). Both paths need:

  1. a web browser inside the streamed desktop to render the auth page, and
  2. a default-browser association so the app's xdg-open <url> call goes somewhere.

The Selkies base image ships no browser and no default handler, so xdg-open has nothing to dispatch to → Google "does nothing", and the email link's final claude://… redirect only works on the machine running the app (the container), which currently can't render/handle it.

Fix (image level)

In claude_desktop/Dockerfile, install a browser and register defaults so the whole flow completes inside the session:

  • apt-get install -y --no-install-recommends chromium (Debian bookworm package name; firefox-esr is an alternative).
  • Set the default browser and confirm the scheme handlers, e.g. as an s6 oneshot or in the desktop autostart (runs as the session user with $DISPLAY/dbus available):
    xdg-settings set default-web-browser chromium.desktop
    xdg-mime default claude-desktop.desktop x-scheme-handler/claude
    
  • Verifying expectations (for reference, not a test step): x-scheme-handler/httpschromium.desktop, x-scheme-handler/claudeclaude-desktop.desktop.

Then login is done entirely in the streamed desktop: Continue with Google opens Chromium in-session and the claude:// redirect lands back in the app; or paste the email magic link into the in-session Chromium (not a phone).

User-side workaround (no rebuild)

  • Add-on Configuration → additional_apps: chromium, restart (installed by rootfs/etc/cont-init.d/80-configuration.sh).
  • Add the two xdg-settings/xdg-mime commands to the custom script /addon_configs/db21ed7f_claude-desktop/claude_desktop.sh (the image ships no standalone terminal).

Problem B — "Your sign-in won't be saved on this device" / recurring re-auth / dispatch shows offline

Symptoms (all three are the same root cause):

  • Install and unlock a system keyring (such as GNOME Keyring), then restart the app.

  • Periodic "For your security, sign in again to keep using Claude."
  • The Claude app's dispatch tab shows this desktop as not online, even though the process is running — until a fresh sign-in is completed from a computer.

Root cause (history)

Claude Desktop (Electron) persists its auth token with safeStorage, which on Linux encrypts via the Secret Service API (org.freedesktop.secrets) provided by gnome-keyring/kwallet. --password-store=gnome-libsecret forces that backend.

This was fixed once (v1.17: gnome-keyring added to the Dockerfile) and then regressed: gnome-keyring was later removed again because, on first boot, it prompts for a keyring password and blocks Claude Desktop from launching at all — a worse failure than a lost session. The launch flag was left forcing libsecret, so with no daemon running safeStorage.isEncryptionAvailable() is false. Confirmed from a live install's ~/.config/Claude/logs/main.log:

[safeStorage] isEncryptionAvailable=false ... (backend=gnome_libsecret) — session will not persist; app secrets fall back to plaintext
Electron safeStorage encryption is not available on this system, cannot store allowlist cache

The un-persisted session goes stale and then fails the elevated-access OAuth check the cowork/dispatch bridge needs:

permission_error: "Session is not fresh enough to grant elevated access. Sign in again to continue." (session_stale_relogin)
[sessions-bridge] Cowork OAuth stale-session (session_stale_relogin); parking bridge until re-login

That "parked" bridge is exactly what the Claude app surfaces as this desktop being offline in the dispatch tab — a fresh sign-in (only completable from a computer, see Problem A) un-parks it, which is why "open from a computer first" appeared to fix it.

Re-adding gnome-keyring would just reintroduce the original launch-blocking prompt, so it's a dead end without also solving that — hence the shipped fix below avoids keyring entirely.

Why v1.35 did not work

v1.35 assumed --password-store=basic makes isEncryptionAvailable() "always true". It does not. On a live install the flag was confirmed on the running process:

$ tr '\0' ' ' < /proc/2247/cmdline
/usr/lib/claude-desktop/claude-desktop --no-sandbox --disable-dev-shm-usage --password-store=basic

and the app still logged, on every launch:

[warn] safeStorage not available, tokens will not persist
[warn] Encryption not available, returning empty env vars
[error] Electron safeStorage encryption is not available on this system, cannot store allowlist cache

Electron deliberately refuses its own basic_text backend unless the application opts in by calling safeStorage.setUsePlainTextEncryption(true) before the ready event. The symbol is present in the shipped Electron binary but absent from resources/app.asar — Claude Desktop never calls it. So v1.35 replaced one unavailable backend with another.

Confirmed against a standalone Electron of the same generation, all with --password-store=basic:

case isEncryptionAvailable()
no opt-in (= Claude Desktop as shipped) false
setUsePlainTextEncryption(true) true, encryptString works
fresh process, decrypting the earlier process's blob true, plaintext recovered

The third row is the one that matters: it is the restart survival this add-on needs.

Fix (shipped, v1.37)

  1. claude_desktop/rootfs/defaults/autostart launches with --password-store=basic instead of gnome-libsecret — Electron's built-in fixed-key store: no daemon, no prompt. Secrets land under $HOME/.config/Claude, and HOME=/data/data is persistent add-on storage, so the session survives restarts. basic trades away OS-backed at-rest protection: unlike gnome-libsecret, its encryption key isn't gated by a keyring daemon, so any process able to read the persistent $HOME/.config/Claude profile can recover the saved credentials. 1b. claude_desktop/rootfs/etc/cont-init.d/86-claude_safestorage.sh + claude_desktop/rootfs/usr/local/bin/claude-safestorage-patch.js (new) supply the opt-in the flag depends on, by injecting safeStorage.setUsePlainTextEncryption(true) into the app's main bundle inside app.asar. The injection goes after the bundle's leading "use strict"; — a directive prologue only counts as the first statement, so prepending ahead of it would drop the main process out of strict mode. The archive is rebuilt (asar headers store per-file offsets, so content cannot simply grow in place), preserving unpacked and symlink entries and recomputing the per-file SHA-256 integrity record for the single changed file; the result is verified and only then renamed over the original. It re-runs every boot after 81-claude_update.sh, because an apt upgrade of claude-desktop ships a fresh unpatched app.asar; it is marker-guarded, so an unchanged app is a no-op, and a failure is logged rather than propagated (an unpatched app still runs, it just forgets the sign-in).
  2. A passwordless keyring was considered instead (keeps libsecret encryption-at-rest without a prompt) and rejected: the keyring DB would live in the same persistent volume as the ciphertext it's "protecting," so it adds ~no real confidentiality in this single-user self-hosted setup, for more moving parts than basic.
  3. claude_desktop/rootfs/etc/cont-init.d/85-openbox_autostart.sh (new): the app is actually launched from the persistent $HOME/.config/openbox/autostart, which the base image's init-selkies-config oneshot only seeds from /defaults/autostart when the persistent copy is missing. Editing /defaults/autostart alone therefore never reaches an existing install's copy. This script re-syncs the persistent copy from the image on every boot (as root, before any s6-rc service starts), so this fix — and any future autostart change — reaches upgrades, not just fresh installs.
  4. gnome-keyring stays out of the Dockerfile.

One-time step after upgrading

The previously-stored session is already stale. Complete one sign-in from a computer (mobile still can't finish the OAuth flow itself, per Problem A) — the session then persists normally and dispatch stays online regardless of which device connects first afterward.


Files this plan touched

  • claude_desktop/rootfs/defaults/autostart — drop the keyring bootstrap; launch with --password-store=basic.
  • claude_desktop/rootfs/etc/cont-init.d/85-openbox_autostart.sh — new in v1.35; syncs the persistent autostart from the image on every boot.
  • claude_desktop/rootfs/etc/cont-init.d/86-claude_safestorage.sh and claude_desktop/rootfs/usr/local/bin/claude-safestorage-patch.js — new in v1.37; the app-side safeStorage opt-in that makes --password-store=basic actually take effect.
  • claude_desktop/Dockerfile — corrected stale comment (gnome-keyring is not installed).
  • claude_desktop/CHANGELOG.md / config.yaml — v1.35, then v1.37.

Problem A (in-desktop browser for OAuth) remains planned-only; not touched by this change.