How Claude HUD Resolves OAuth Credentials: Prioritizing macOS Keychain Over Legacy Files
Claude HUD resolves OAuth credentials by first querying the macOS Keychain for Claude Code 2.x tokens, then falling back to legacy .credentials.json files, and finally returning null for API-only mode if neither source provides a valid, non-expired token.
The credential resolution system in jarrodwatts/claude-hud is designed to authenticate with Anthropic’s usage API securely. Implemented primarily in src/usage-api.ts, the logic follows a strict priority hierarchy that favors modern secure storage while maintaining backward compatibility with older Claude Code installations.
Credential Resolution Priority Order
The readCredentials function orchestrates a three-tier lookup strategy that determines how the HUD obtains valid access tokens.
macOS Keychain Lookup (Claude Code 2.x)
The primary resolution path targets the macOS Keychain using profile-specific service names. The helper readKeychain (lines 644–660) executes the native security command to retrieve stored credentials:
// src/usage-api.ts - readKeychain implementation concept
async function readKeychain(service: string): Promise<string | null> {
try {
const { execSync } = require('child_process');
const raw = execSync(`security find-generic-password -s "${service}" -w`, {
encoding: 'utf8',
}).trim();
return raw || null;
} catch {
return null; // No entry, or not on macOS
}
}
When readCredentials calls this helper, it first determines the correct service name via determineServiceName (line 523), which generates Claude Code-${profile} or falls back to the legacy constant CLAUDE_CODE_LEGACY_SERVICE. If the keychain contains a valid JSON blob with an expires_at timestamp in the future, the function immediately returns the token and logs debug('Using credentials from macOS Keychain') at line 785.
Legacy File-Based Credentials (Pre-2.x)
When the keychain lookup fails—either because the user is on a non-macOS platform or has not migrated to the new storage system—the code falls back to reading the legacy credentials file located at {CLAUDE_CONFIG_DIR}/.credentials.json. The readFileCredentials function (lines 703–734) handles this by:
- Resolving the config directory via
getClaudeConfigDirfromsrc/config.ts - Parsing the JSON file through
parseCredentialsData - Validating that the stored token contains both
access_tokenandsubscription_typefields
Successful retrieval triggers the log entry debug('Using credentials from file') at line 802.
Graceful Degradation to API-Only Mode
If neither the keychain nor the legacy file yields a usable, non-expired token, readCredentials returns null. Downstream callers interpret this absence as a signal to enter "API-only" mode, skipping the usage API request entirely rather than failing with authentication errors.
Core Implementation Details in usage-api.ts
The resolution flow is centralized in src/usage-api.ts, with specific responsibilities distributed across several functions:
determineServiceName(line 523): Generates the keychain service identifier based on the active profilereadKeychain(lines 644–660): Wraps thesecurity find-generic-passwordCLI command to extract the JSON credential blobreadFileCredentials(lines 703–734): Handles filesystem I/O for the legacy.credentials.jsonpathreadCredentials(lines 770–815): Orchestrates the priority queue, callingreadKeychainfirst, thenreadFileCredentialson failuregetOAuthCredentials(lines 598–620): The public API exported for the HUD render pipeline to consume
The public API can be invoked as follows:
import { getOAuthCredentials } from './usage-api';
// Inside the HUD render loop:
const now = Date.now();
const { credentials, shouldBackoff } = await getOAuthCredentials(
os.homedir(),
now,
{ readKeychain } // injected platform-specific keychain reader
);
if (credentials) {
// credentials.accessToken can be sent to Anthropic's usage endpoint
console.log('✅ OAuth token loaded from', credentials.source);
} else {
console.warn('⚠️ No OAuth credentials found – falling back to API-only mode');
}
Why macOS Keychain Takes Precedence
Claude Code stores OAuth credentials in the user’s system keychain to leverage encrypted storage and automatic secret lifecycle management. By checking the keychain first, Claude HUD respects the most recent, secure storage method introduced in Claude Code 2.x while remaining backward-compatible with older installations that persisted credentials on disk. This design ensures that macOS users benefit from hardware-backed encryption without breaking functionality for Linux or Windows users who rely on the legacy file-based approach.
Summary
- Priority order: macOS Keychain → Legacy
.credentials.jsonfile →null(API-only mode) - Key file:
src/usage-api.tscontains the complete resolution logic from lines 598–815 - Security command: Uses
security find-generic-password -s <service> -wfor keychain access - Legacy path: Resolves to
{CLAUDE_CONFIG_DIR}/.credentials.jsonviasrc/config.ts - Graceful failure: Returns
nullrather than throwing when no valid credentials exist, allowing the HUD to operate in degraded mode
Frequently Asked Questions
How does Claude HUD determine which keychain service name to query?
The function determineServiceName (line 523 in src/usage-api.ts) constructs the service identifier by interpolating the active profile name into the format Claude Code-${profile}. If no profile is specified, it falls back to the legacy constant CLAUDE_CODE_LEGACY_SERVICE defined in src/constants.ts.
What happens if the OAuth token stored in the keychain has expired?
During the parsing phase within readCredentials, the code checks the expires_at timestamp against the current time. If the token is expired, the function treats the keychain entry as invalid and proceeds to the legacy file fallback. If the file also contains expired or invalid credentials, the function returns null, forcing the HUD into API-only mode.
Can Claude HUD retrieve credentials on Linux or Windows systems?
Yes, through the legacy file fallback. Since the readKeychain helper relies on the macOS-specific security CLI tool, it returns null on non-macOS platforms. The system then automatically attempts to read {CLAUDE_CONFIG_DIR}/.credentials.json, allowing Linux and Windows users to authenticate using file-based storage.
What is the performance impact of checking the keychain before every API call?
The resolution occurs once per HUD render cycle via getOAuthCredentials, which caches the result for the duration of the operation. The native security command executes quickly (typically sub-millisecond), and the implementation includes backoff logic via the shouldBackoff return value to prevent excessive keychain queries during authentication failures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →