# How Claude HUD Resolves OAuth Credentials: Prioritizing macOS Keychain Over Legacy Files

> Discover how Claude HUD resolves OAuth credentials, securely prioritizing macOS Keychain over legacy files for robust authentication. Learn more.

- Repository: [Jarrod Watts/claude-hud](https://github.com/jarrodwatts/claude-hud)
- Tags: deep-dive
- Published: 2026-03-18

---

**Claude HUD resolves OAuth credentials by first querying the macOS Keychain for Claude Code 2.x tokens, then falling back to legacy [`.credentials.json`](https://github.com/jarrodwatts/claude-hud/blob/main/.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`](https://github.com/jarrodwatts/claude-hud) is designed to authenticate with Anthropic’s usage API securely. Implemented primarily in [`src/usage-api.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/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:

```typescript
// 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:

1. Resolving the config directory via `getClaudeConfigDir` from [`src/config.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/config.ts)
2. Parsing the JSON file through `parseCredentialsData`
3. Validating that the stored token contains both `access_token` and `subscription_type` fields

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`](https://github.com/jarrodwatts/claude-hud/blob/main/src/usage-api.ts), with specific responsibilities distributed across several functions:

- **`determineServiceName`** (line 523): Generates the keychain service identifier based on the active profile
- **`readKeychain`** (lines 644–660): Wraps the `security find-generic-password` CLI command to extract the JSON credential blob
- **`readFileCredentials`** (lines 703–734): Handles filesystem I/O for the legacy [`.credentials.json`](https://github.com/jarrodwatts/claude-hud/blob/main/.credentials.json) path
- **`readCredentials`** (lines 770–815): Orchestrates the priority queue, calling `readKeychain` first, then `readFileCredentials` on failure
- **`getOAuthCredentials`** (lines 598–620): The public API exported for the HUD render pipeline to consume

The public API can be invoked as follows:

```typescript
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.json`](https://github.com/jarrodwatts/claude-hud/blob/main/.credentials.json) file → `null` (API-only mode)
- **Key file**: [`src/usage-api.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/usage-api.ts) contains the complete resolution logic from lines 598–815
- **Security command**: Uses `security find-generic-password -s <service> -w` for keychain access
- **Legacy path**: Resolves to `{CLAUDE_CONFIG_DIR}/.credentials.json` via [`src/config.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/config.ts)
- **Graceful failure**: Returns `null` rather 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`](https://github.com/jarrodwatts/claude-hud/blob/main/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`](https://github.com/jarrodwatts/claude-hud/blob/main/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.