Skip to content

macOS: credentials live in login keychain, not ~/.claude/.credentials.json — switcher fails #3

Description

@shackstack

Summary

On macOS, Claude Code stores its OAuth credentials in the login keychain (generic password, service Claude Code-credentials), not in ~/.claude/.credentials.json. This switcher only ever reads/writes that file, so on a default macOS install it cannot work:

  • listing / syncing fails with ENOENT: ... open '~/.claude/.credentials.json'
  • even if a switch "succeeds", it writes a file that Claude Code never reads, so the active account never actually changes.

The README lists macOS as supported, but the keychain case isn't handled.

Reproduction

Fresh macOS install of Claude Code (credentials in keychain, no .credentials.json on disk):

npx claude-code-multi-accounts install
cc-switch
# Switch failed: ENOENT: no such file or directory, open '/Users/<me>/.claude/.credentials.json'

Confirm credentials are in the keychain:

security find-generic-password -s "Claude Code-credentials" -w
# -> {"claudeAiOauth":{...},"mcpOAuth":{...}}

Cause

bin/lib/store/io.cjs hardcodes the file path and uses fs for all credential I/O:

function getDefaultCredentialsPath() {
  return path.join(os.homedir(), '.claude', '.credentials.json');
}
// readJson / writeJson / backupFile all assume a real file on disk

There's no keychain code path, so on macOS the credentials are invisible to the tool.

Suggested fix

When process.platform === 'darwin' and the credentials file is absent, bridge credential I/O to the keychain via the security CLI:

  • read: security find-generic-password -s "Claude Code-credentials" -w
  • write: security add-generic-password -U -s "Claude Code-credentials" -a <account> -w <json>
    (-U updates the existing item; <account> is the existing item's acct)
  • backup: dump the current keychain value to the backup dir before overwriting (the file backupFile step has no effect when there's no file).

The stored value already has the exact shape the tool expects ({ claudeAiOauth, mcpOAuth }credentials.claudeAiOauth), so only the I/O layer needs to change; the rest of the switch logic works unchanged.

I patched io.cjs locally along these lines and the full flow (sync / list / switch, with pre-switch keychain backup) now works on macOS, preserving both claudeAiOauth and mcpOAuth. Happy to send a PR if useful.

Environment

  • macOS (Darwin), Node.js v25.9.0
  • Credentials in login keychain (Claude Code-credentials), no ~/.claude/.credentials.json
  • Internal STORE_VERSION 0.2.9

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions