OAuth login flow unusable over SSH on macOS — Keychain inaccessible, no reliable remote auth path for Pro/Max subscribers

Resolved 💬 3 comments Opened Apr 6, 2026 by brianjmalone Closed Apr 9, 2026

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?

What's Wrong?
Claude Code on macOS stores OAuth credentials exclusively in the macOS Keychain, which is inaccessible from SSH sessions (errSecInteractionNotAllowed, exit code 36). This makes Claude Code unusable over SSH for Pro/Max subscription users unless an undocumented .credentials.json fallback file happens to exist — and there is no documented or reliable way to create or maintain that file.
Environment

Claude Code version: (latest)
Platform: macOS 15.x (Apple Silicon Mac Mini M4 + iMac, both headless via SSH)
SSH client: Termius on iOS
Network: Tailscale overlay network
Subscription: Max (OAuth, not API key)

Setup
Standard remote development workflow:

Mac Mini M4 and iMac, both accessible via Tailscale
iPhone connects via Termius (SSH) over Tailscale from any network
Claude Code runs in the SSH session

Root Cause
Claude Code stores OAuth tokens in the macOS Keychain under the service name "Claude Safe Storage" with account "Claude Key". SSH sessions cannot access Keychain items due to per-item ACLs that require GUI context:
bash# From local terminal — works (with password prompts):
security find-generic-password -s "Claude Safe Storage" -a "Claude Key" -w

From SSH — fails silently:

security find-generic-password -s "Claude Safe Storage" -a "Claude Key" -w

exit code 36 (errSecInteractionNotAllowed)

Claude Code has a file-based credential fallback (~/.claude/.credentials.json) that works over SSH when the file exists. However:

The file does not exist by default. A fresh claude login from a local terminal writes only to Keychain, not to the file.
The file appeared spontaneously during debugging after triggering a Keychain unlock via the security command from a local terminal. The mechanism that creates it is undocumented.
There is no documented way to generate or refresh this file.
Token expiry is unknown. The tokens in the file have an expiresAt field (~24 hours observed). It is unclear whether Claude Code refreshes the file when the token expires, or whether refresh only works through the Keychain (which SSH can't access).
The file could be deleted by Claude Code at any time — updates, cleanup processes, logout — since its lifecycle is undocumented.

Steps to Reproduce

Install Claude Code on macOS, authenticate via claude login (browser OAuth flow completes successfully)
Verify Claude Code works from a local terminal
SSH into the same machine (via Termius, standard SSH, etc.)
Run claude — demands /login
/login attempts to open a browser → fails (no display over SSH)
/login provides a fallback URL (~23,000 characters) → URL is unusable from a mobile terminal (line breaks/truncation corrupt it on copy-paste)
No path forward without physical access to the machine

Partial Workaround Found
If the Keychain is unlocked from a local terminal session (e.g., by running security find-generic-password and entering the Keychain password), Claude Code appears to write ~/.claude/.credentials.json. Subsequent SSH sessions can then read this file and authenticate without /login. This workaround:

Requires physical/GUI access to the machine
Must be done before leaving the machine (no way to do it remotely)
Has unknown durability (token expiry, file persistence)
Is completely undocumented

Expected Behavior
Two things should work, and neither does:

  1. File-based credentials should be first-class on macOS

When claude login completes, credentials should be written to ~/.claude/.credentials.json in addition to (or instead of) the Keychain, just as they are on Linux. The current behavior — Keychain-only storage that silently fails over SSH — makes Claude Code incompatible with SSH by design.

  1. Device-code auth flow (RFC 8628)

Authentication should be completable entirely within the terminal via a short device code, following every other major CLI tool:
$ claude /login
No browser detected. Authenticate from another device:

Open: https://claude.ai/device
Code: ABCD-1234

Waiting for authentication... ✓ Authenticated.
This pattern is used by: gh auth login, az login --use-device-code, aws sso login, gcloud auth login, kubectl.
Why This Matters

Pro/Max subscribers pay for Claude Code but cannot use it over SSH — a completely standard development workflow
The only alternative (ANTHROPIC_API_KEY) bypasses the subscription and bills separately — users should not have to pay twice
macOS is a primary development platform and SSH access is routine
The Keychain-only credential storage is a macOS-specific regression — Linux users get file-based credentials by default

Related Issues

#22992 — Device-code auth flow request (RFC 8628)
#9403 — macOS Keychain service name mismatch
#5957 — SSH auth failure after Keychain integration (called out as a regression)
#29816 — SSH sessions require re-login despite valid .credentials.json
#10158 — SSH sessions can't authenticate on macOS (extensive workaround testing)
#7100 — Headless/remote auth documentation
#36807 — Remote control token expiry requiring interactive re-login
#24317 — Frequent re-auth with concurrent sessions
#21765 — Refresh token not used on remote machines

What Should Happen?

Suggested Fix

Immediate: Write credentials to ~/.claude/.credentials.json on claude login on macOS, matching Linux behavior. This is a small change that unblocks all SSH users.
Proper: Implement RFC 8628 device authorization grant. This is a solved problem. Short code, simple URL, works from any device, no clipboard gymnastics.

Error Messages/Logs

n/a

Steps to Reproduce

Steps to Reproduce

Install Claude Code on macOS, authenticate via claude login (browser OAuth flow completes successfully)
Verify Claude Code works from a local terminal
SSH into the same machine (via Termius, standard SSH, etc.)
Run claude — demands /login
/login attempts to open a browser → fails (no display over SSH)
/login provides a fallback URL (~23,000 characters) → URL is unusable from a mobile terminal (line breaks/truncation corrupt it on copy-paste)
No path forward without physical access to the machine

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.92 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

_No response_

View original on GitHub ↗

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