Skip to content

feat(oauth_claude): add macOS Keychain support for credential storage#38

Merged
CaddyGlow merged 5 commits intoCaddyGlow:mainfrom
hanzili:feature/macos-keychain-support
Feb 4, 2026
Merged

feat(oauth_claude): add macOS Keychain support for credential storage#38
CaddyGlow merged 5 commits intoCaddyGlow:mainfrom
hanzili:feature/macos-keychain-support

Conversation

@hanzili
Copy link
Contributor

@hanzili hanzili commented Feb 1, 2026

Summary

Add macOS Keychain fallback to ClaudeOAuthStorage.load() for reading Claude OAuth credentials.

Problem

Claude Code stores OAuth credentials in macOS Keychain and intentionally deletes the plain text ~/.claude/.credentials.json file for security. This is documented behavior since v1.0.5:

"This is intended behavior (cleaning up plain text credentials when successfully storing them in macos keychain)"

As a result, CCProxy fails to load credentials on macOS:

ValueError: No valid OAuth access token available for Claude API

Solution

  • Try reading from ~/.claude/.credentials.json first (existing behavior)
  • Fall back to macOS Keychain using the security command
  • Use the service name "Claude Code-credentials" (same as Claude Code)
  • Log which source was used (source=file or source=keychain)

Changes

  • Added _read_from_macos_keychain() async helper function
  • Modified ClaudeOAuthStorage.load() to try Keychain when file is missing
  • Added timeout and error handling for subprocess call
  • Platform check ensures this only runs on macOS (Darwin)

Testing

Tested locally on macOS with:

  1. Removed ~/.claude/.credentials.json
  2. Verified credentials exist in Keychain: security find-generic-password -s "Claude Code-credentials" -w
  3. Started CCProxy - logs show claude_oauth_credentials_loaded_from_keychain
  4. Made API calls successfully through CCProxy

References


🤖 Generated with Claude Code

hanzili and others added 3 commits January 31, 2026 22:29
Claude Code stores OAuth credentials in macOS Keychain and intentionally
deletes the plain text ~/.claude/.credentials.json file for security.
This is documented behavior since Claude Code v1.0.5.

This change adds Keychain fallback to ClaudeOAuthStorage.load():
- Try reading from file first (all platforms, manual setups)
- Fall back to macOS Keychain using `security` command
- Log which source was used for debugging

Fixes credential loading on macOS where the file doesn't exist.

Reference: anthropics/claude-code#1414

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Break long list in subprocess.run() call to stay within 88-char limit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@CaddyGlow CaddyGlow assigned Copilot and CaddyGlow and unassigned Copilot Feb 4, 2026
…tial storage

Replace subprocess-based macOS Keychain access with the keyring library,
enabling cross-platform support for macOS Keychain, Windows Credential
Manager, and Linux Secret Service. Adds comprehensive unit tests for
the keychain functionality.
@CaddyGlow
Copy link
Owner

Refactored to use keyring library

Replaced the subprocess-based security command with the cross-platform keyring library.

Changes

Implementation (storage.py):

  • Uses keyring.get_password() instead of subprocess.run() with the security CLI
  • Service name: "Claude Code" with account "credentials" (matching Claude Code's storage)
  • Added _is_keyring_available() for graceful degradation when keyring isn't installed
  • Improved exception handling with specific error types
  • Type-safe JSON parsing (validates dict type instead of casting)

Cross-platform support:

  • macOS Keychain
  • Windows Credential Manager
  • Linux Secret Service (GNOME Keyring, KDE Wallet)

Tests (test_storage.py):

  • 15 unit tests covering keychain availability, read success/failure, fallback behavior, and error handling

Dependencies

  • Added keyring>=25.0.0 to plugins-claude optional dependencies

Note

Did not have access to a macOS machine to test the keyring integration against the actual macOS Keychain. Please verify on macOS before merging.

Ensures keyring is installed during CI type checking so mypy can
resolve the import in oauth_claude storage module.
@CaddyGlow CaddyGlow merged commit 3f85de7 into CaddyGlow:main Feb 4, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants