Skip to content

Instantly share code, notes, and snippets.

@regme
Forked from fortunto2/README.md
Created March 31, 2026 20:48
Show Gist options
  • Select an option

  • Save regme/4ca119b99b492e1ef3c11029d6ec1de7 to your computer and use it in GitHub Desktop.

Select an option

Save regme/4ca119b99b492e1ef3c11029d6ec1de7 to your computer and use it in GitHub Desktop.
Claude Code multi-account auth switcher — switch between personal/work/API key profiles via macOS Keychain

Claude Code Multi-Account Auth Switcher

Switch between multiple Claude Code subscriptions (personal / work / API key) on macOS using Keychain.

Lightweight alternative (~80 lines of bash) to heavier solutions. No dependencies beyond Python 3 and macOS security CLI.

How it works

Three pieces:

  1. claude-switch-auth — main script, swaps Keychain credentials between saved profiles
  2. claude() wrapper in .zshrc — auto-switches profile based on current directory
  3. .envrc (direnv) — isolates Claude config per project (CLAUDE_CONFIG_DIR)

Supports both OAuth credentials (Claude Code-credentials service) and API key credentials (Claude Code service) — the correct keychain service is detected and stored per profile.

Setup

# 1. Put the script in PATH
cp claude-switch-auth ~/bin/
chmod +x ~/bin/claude-switch-auth

# 2. Save your current auth as a profile
claude-switch-auth save personal

# 3. Log into another account in Claude Code, then save it too
claude-switch-auth save work

# 4. (Optional) Add the wrapper to ~/.zshrc for auto-switching by directory
# See zshrc-wrapper.sh

# 5. (Optional) Per-project config isolation via direnv
# See dot-envrc

Usage

claude-switch-auth list          # show saved profiles with service type
claude-switch-auth personal      # switch to personal
claude-switch-auth work          # switch to work
claude-switch-auth status        # show current subscription/tier and service type
claude-switch-auth save <name>   # save current keychain to profile

With the .zshrc wrapper, switching is automatic — just cd into the project directory and run claude.

OAuth vs API Key

Claude Code uses different keychain service names depending on auth method:

Auth method Keychain service name
OAuth (Pro/Max subscription) Claude Code-credentials
API key Claude Code

This script detects and stores the service name per profile, so you can freely mix OAuth and API key profiles.

Requirements

  • macOS (uses security CLI for Keychain)
  • Python 3 (for JSON parsing and Keychain manipulation)
  • direnv (optional, for config isolation)

Security

  • Profile files stored in ~/.claude-auth-profiles/ with chmod 600
  • Credentials never leave macOS Keychain except during profile swap
  • No tokens in plain text, environment variables, or git

Alternatives

License

MIT

#!/bin/bash
# Switch Claude Code keychain credentials between saved profiles.
# Supports both OAuth ("Claude Code-credentials") and API key ("Claude Code") auth.
#
# Usage:
# claude-switch-auth save <name> — save current keychain to profile
# claude-switch-auth <name> — load profile into keychain
# claude-switch-auth status — show active profile
# claude-switch-auth list — list saved profiles
KEYCHAIN_ACCOUNT="${USER:-$(whoami)}"
CREDS_DIR="$HOME/.claude-auth-profiles"
# Detect which keychain service is active
get_service_name() {
if security find-generic-password -s "Claude Code-credentials" >/dev/null 2>&1; then
echo "Claude Code-credentials"
elif security find-generic-password -s "Claude Code" >/dev/null 2>&1; then
echo "Claude Code"
else
echo ""
fi
}
# Read the service name stored with a profile (fallback to default)
get_profile_service() {
local profile="$1"
local meta="$CREDS_DIR/${profile}.meta"
if [[ -f "$meta" ]]; then
cat "$meta"
else
echo "Claude Code-credentials"
fi
}
switch_profile() {
local profile="$1"
[[ ! -f "$CREDS_DIR/$profile" ]] && echo "Profile '$profile' not found. Run: claude-switch-auth save $profile" && exit 1
local target_service
target_service=$(get_profile_service "$profile")
local current_service
current_service=$(get_service_name)
python3 -c "
import subprocess
pw = open('$CREDS_DIR/$profile').read().strip()
# Remove old service entry if switching between OAuth/API key modes
for svc in ['Claude Code-credentials', 'Claude Code']:
subprocess.run(['security', 'delete-generic-password', '-s', svc, '-a', '$KEYCHAIN_ACCOUNT'], capture_output=True)
# Add credentials under the correct service name
subprocess.run(['security', 'add-generic-password', '-s', '$target_service', '-a', '$KEYCHAIN_ACCOUNT', '-w', pw], check=True)
"
echo "Switched to: $profile (service: $target_service)"
}
case "$1" in
save)
PROFILE="$2"
[[ -z "$PROFILE" ]] && echo "Usage: claude-switch-auth save <name>" && exit 1
mkdir -p "$CREDS_DIR"
SERVICE=$(get_service_name)
if [[ -z "$SERVICE" ]]; then
echo "Error: No Claude Code credentials found in keychain"
exit 1
fi
security find-generic-password -s "$SERVICE" -a "$KEYCHAIN_ACCOUNT" -w 2>/dev/null > "$CREDS_DIR/$PROFILE"
echo "$SERVICE" > "$CREDS_DIR/${PROFILE}.meta"
chmod 600 "$CREDS_DIR/$PROFILE" "$CREDS_DIR/${PROFILE}.meta"
echo "Saved profile: $PROFILE (service: $SERVICE)"
;;
status)
SERVICE=$(get_service_name)
if [[ -z "$SERVICE" ]]; then
echo "No Claude Code credentials found"
exit 1
fi
echo "Service: $SERVICE"
security find-generic-password -s "$SERVICE" -a "$KEYCHAIN_ACCOUNT" -w 2>/dev/null | \
python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
o = d.get('claudeAiOauth', {})
if o:
print(f'Type: OAuth — {o.get(\"subscriptionType\", \"?\")} / {o.get(\"rateLimitTier\", \"?\")}')
elif d.get('claudeAiApiKey'):
print('Type: API Key')
else:
print('Type: Unknown')
except: print('Could not parse credentials')
"
;;
list)
if [[ -d "$CREDS_DIR" ]]; then
for f in "$CREDS_DIR"/*; do
[[ "$f" == *.meta ]] && continue
[[ -f "$f" ]] || continue
name=$(basename "$f")
service=$(get_profile_service "$name")
echo " $name ($service)"
done
else
echo "No profiles saved"
fi
;;
"")
echo "Usage: claude-switch-auth {save <name>|<name>|status|list}"
;;
*)
switch_profile "$1"
;;
esac
# .envrc — put in project root, requires direnv
# Isolates Claude Code config (history, settings) per project
export CLAUDE_CONFIG_DIR=$HOME/.claude-epiphan
# Add to ~/.zshrc — auto-switch Claude auth profile by directory
claude() {
if [[ "$PWD" == ~/projects/epiphan* ]]; then
claude-switch-auth epiphan 2>/dev/null
else
claude-switch-auth personal 2>/dev/null
fi
command claude "$@"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment