claude setup-token silently fails on headless Linux servers (no system keyring)

Resolved 💬 3 comments Opened May 4, 2026 by trixdaddy Closed May 8, 2026

Summary

claude setup-token runs the OAuth browser flow successfully and exits cleanly, but no credentials are persisted on a headless Linux server lacking a system keyring. The next claude -p ... invocation still returns Not logged in · Please run /login.

Environment

  • claude version: 2.1.126 (also reproduced on 2.1.x older builds)
  • OS: Ubuntu 24.04 LTS (server install — no desktop session)
  • No keyring daemon installed: secret-tool, gnome-keyring-daemon, kwalletd6 all absent
  • Same OAuth flow on a desktop Ubuntu host (KDE + kwalletd6 running) succeeds and writes ~/.claude/.credentials.json as plain JSON

Reproduction

On a headless Linux VM with no keyring daemon installed:

\\\`bash
$ claude setup-token

Browser flow opens, code is pasted, command exits with no error

$ ls -la ~/.claude/.credentials.json
ls: cannot access '/home/user/.claude/.credentials.json': No such file or directory
$ claude -p "ping"
Not logged in · Please run /login
\\\`

Expected behavior

Either:

  1. Detect missing keyring at the start of setup-token and warn loudly (\"no keyring backend; will write to ~/.claude/.credentials.json with mode 0600 — OK?\"), then write to FS
  2. Always prefer FS storage on Linux (the desktop host already does this — both desktop and server were on the same client version), making behavior consistent
  3. At minimum, fail visibly when the keychain write fails so the user knows OAuth didn't actually persist

Currently the silent-fail mode wastes the OAuth browser dance and leaves users guessing why claude -p still says \"not logged in\".

Workaround

The token is account-bound, not device-bound, so copying ~/.claude/.credentials.json from a desktop machine where OAuth landed correctly works:

\\\bash
scp ~/.claude/.credentials.json server:~/.claude/.credentials.json
ssh server 'chmod 600 ~/.claude/.credentials.json'
\
\\

After this, claude -p \"ping\" returns the response immediately. Caveat: when OAuth refreshes silently in the desktop's claude session, the server's copy goes stale; needs periodic re-scp.

Why this matters

Anyone deploying Claude Code on a server (CI workers, autonomous-agent runtimes, headless dev VMs, etc.) hits this exact silent-fail. The standard advice — \"run \claude setup-token\\" — fails silently, and the FS fallback only happens to work because the desktop install path differs.

Suggested fix

In the OAuth-persist code path, after the keychain write call:

  1. Verify the credential is actually retrievable (round-trip read), or
  2. If keyring backend reports unavailable / no-op, fall through to FS storage with a chmod 0600 and a clear message to the user about the storage location.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗