the KodeLab

macOS SSH and Claude Code "Logged Out": The Keychain Unlock Fix

1,319 words 7 min read
macOS SSH and Claude Code "Logged Out": The Keychain Unlock Fix

I recently had some long-running jobs running on the mac mini at home, so I started SSH’ing into it from my laptop to drive things — and as soon as I started Claude Code on the remote side, I got the /login prompt. The puzzling part: the night before I had logged into Claude Code from the mac mini’s local Terminal and was using it just fine. SSH’ing in and tmux attach-ing back to the original session occasionally hit the same “logged out” state too (less often than a fresh SSH, but it definitely happened). My first guess was a Claude Code bug, but a few rounds of testing showed that running security unlock-keychain once was enough to fix it. That makes it not a Claude Code problem but a property of the macOS login keychain — anything that stores tokens or credentials there can hit the same wall.

TL;DR — one command

After SSH’ing into the Mac, run the command below. It’ll prompt for your login password; once it succeeds, just restart Claude Code and it’ll work without forcing /login again.

# Unlock the current user's login keychain
security unlock-keychain ~/Library/Keychains/login.keychain-db

# The path is optional — login keychain is the default
security unlock-keychain

If you want to check the current state first, security show-keychain-info shows the idle auto-lock interval, and security list-keychains lists which keychains are currently mounted.

Why SSH triggers it

The macOS login keychain is an encrypted file — it stores all the passwords, tokens, and certificates that applications write into Keychain. Its encryption key is your account login password, which means it has to be unlocked before its contents can be read or written. The usual unlock moment: when you sit in front of the Mac and type your password into the login screen, loginwindow automatically uses that password to unlock the login keychain. After that, as long as you don’t log out and the auto-lock hasn’t fired, any process the same user starts can read keychain contents through the Keychain Services API.

The problem is that SSH doesn’t go through loginwindow. After sshd authenticates you (via PAM with a password or public key) it just hands you a shell — nothing in that path triggers a keychain unlock. So the SSH session has the right user identity, but when Claude Code tries to read its login token from the keychain, the keychain is locked and the read fails. The visible symptom is “please log in again.”

As for why tmux is occasionally affected: the tmux server is a long-running process, usually started the first time you opened a GUI Terminal — at that point the keychain was already unlocked, so any session attached afterwards inherits the unlocked state. But once the keychain locks at some point — idle longer than show-keychain-info’s timeout, manually locked, locked on sleep depending on settings, or the tmux server itself restarted — re-attaching from SSH behaves the same as a plain SSH session: the token can’t be read.

Why security unlock-keychain works

security is the built-in macOS CLI for talking to Keychain Services, and unlock-keychain unlocks the named keychain (defaulting to login keychain) using your password. The unlock is shared across processes — it’s not scoped to the current shell, but to the current user’s subsequent processes. So once we unlock from the SSH session, any Claude Code we start afterwards can read its token.

A few related commands worth knowing:

# Inspect auto-lock settings (idle seconds, lock-on-sleep)
security show-keychain-info ~/Library/Keychains/login.keychain-db

# Set idle auto-lock to 2 hours and disable lock-on-sleep
security set-keychain-settings -t 7200 ~/Library/Keychains/login.keychain-db

# Lock manually (the inverse)
security lock-keychain ~/Library/Keychains/login.keychain-db

# Pass the password inline with -p (lands in shell history — not recommended)
security unlock-keychain -p 'your-password' ~/Library/Keychains/login.keychain-db

The -p flag with a literal password is dangerous — it ends up in shell history and the process list. Stick to interactive entry, or wrap the command in a script that reads from a permission-protected file and pipes through stdin.

It’s not just Claude Code

Anything that stores tokens, passwords, or credentials in the login keychain can show the same symptom under SSH or in a tmux session whose keychain has been locked. Here’s a quick reference table — hopefully this helps people who hit the same problem with other tools find this post:

Tool / scenarioSymptom when keychain is lockedNotes
Claude Code/login prompt on launch, or 401 on agent API callsTokens stored in login keychain
GitHub CLI (gh)gh auth status shows not logged in, gh auth token returns nothingWhen using --secure-storage or the macOS default
git + osxkeychain helperHTTPS push/pull keeps prompting for passwordgit config --global credential.helper osxkeychain
Docker Desktop / docker logindocker pull on private images returns 401Credentials store uses osxkeychain
AWS CLI / aws-vaultProfile credentials missing, MFA tokens unreadableaws-vault uses keychain as the default backend
npm / yarn / pnpmnpm publish 401, registry auth tokens unreadableSame applies to any Electron tool using keytar
1Password CLI (op)Session expired, biometric unlock unavailableBiometric unlock requires GUI assistance
Xcode codesign / notarytoolSigning or notarization can’t find dev certs, errSecInternalComponentMost common when CIs build over SSH
fastlane matchCertificate import fails or repeatedly prompts for passwordsOfficial docs recommend unlock-keychain first on CI
VS Code / Cursor Remote-SSHGitHub, Copilot, sync state lostThe remote Electron can’t read the local keychain
Other Electron CLIs (keytar)Various token failurespnpm, Slack CLI, Firebase CLI, etc.

Longer-term workarounds

Make a convenient alias

If you just want to type less, add this to ~/.zshrc and unlock manually whenever it bites:

alias unlock='security unlock-keychain ~/Library/Keychains/login.keychain-db'

Stretch the auto-lock timeout

If the mac mini is your own personal machine on a private network, lengthening the idle timeout or disabling lock-on-sleep cuts down on how often this fires. This trades security for convenience, so don’t do it on shared machines.

# -t seconds = idle timeout; omitting -l means don't lock on sleep
security set-keychain-settings -t 28800 ~/Library/Keychains/login.keychain-db

Keep the Mac in a GUI-logged-in state

If you’re using a Mac as a server, turn on auto-login in System Settings → Users — loginwindow will unlock the login keychain at boot, and every SSH session afterwards inherits the unlocked state. Screen lock and keychain lock are technically separate, but their idle timeouts and sleep settings interact, so combining auto-login with the set-keychain-settings command above tends to be the most stable setup.

Auto-unlock on SSH (be careful)

You can also put expect or a permission-protected file read into ~/.zprofile to auto-unlock. This is functionally equivalent to leaving your login password on disk — the risk is the same as whatever the keychain was supposed to protect. Realistically, only do this on tightly controlled single-user machines. The compromise I’ve used: store the password in 1Password on a different machine, then on SSH use op read to fetch it and pipe it into security unlock-keychain, so the password only ever lives in memory briefly.

Wrap-up

The one-liner to remember: the login keychain only auto-unlocks on GUI login, not on SSH. Any tool that stores tokens or credentials in macOS Keychain can show up as “logged out” or “credentials expired” inside an SSH session or a partially-affected tmux session. When it happens, run security unlock-keychain first; long-term, combine it with longer auto-lock intervals or auto-login to reduce the frequency. If you want to dig deeper into how tmux sessions actually work, take a look at our tmux multiplexer tutorial (繁體中文).