---
summary: "Claude fetch behavior comparison covering OAuth, web, CLI, and Keychain prompt changes."
read_when:
  - Reviewing Claude fetch regressions since 0.18.0 beta 2
  - Changing Claude OAuth, web, CLI, or Keychain prompt behavior
  - Comparing old and current Claude credential flows
---

# Claude Fetch Comparison (`7b79b2d` vs `HEAD`)

This document compares Claude data fetching behavior between:
- Baseline commit: `7b79b2d080c6b00f6c8f52f89ac115f33a7ca8b0`
- Current `HEAD`: `37841489f849567a598d2a8ba601eb6f1228644e`

Focus areas:
- OAuth
- Web
- CLI
- Keychain permission prompts, cooldowns, and re-prompt behavior
- OAuth token fetch/use/refresh behavior

## High-level Changes

- OAuth moved from "load token and fail if expired" to "auto-refresh when expired".
- Claude keychain reads now use stricter non-interactive probes (`LAContext.interactionNotAllowed`) before any promptable path.
- New Claude keychain prompt cooldown gate: 6-hour suppression after denial.
- New OAuth refresh failure gate:
  - `invalid_grant` => terminal block until auth fingerprint changes.
  - repeated `400/401` (non-`invalid_grant`) => transient exponential backoff (up to 6h).
- Auto-mode availability checks were hardened to avoid interactive prompts where possible.

## OAuth Flow

### OAuth (Before: `7b79b2d`)

```mermaid
flowchart TD
    A["Claude OAuth fetch starts"] --> B["load(environment)"]
    B --> C{"token found?"}
    C -- "no" --> K["OAuth not available / fail"]
    C -- "yes" --> D{"token expired?"}
    D -- "yes" --> E["Fail: token expired (run claude)"]
    D -- "no" --> F{"has user:profile scope?"}
    F -- "no" --> G["Fail: missing scope"]
    F -- "yes" --> H["GET /api/oauth/usage with Bearer access token"]
    H --> I{"HTTP success?"}
    I -- "yes" --> J["Map usage response -> snapshot"]
    I -- "no" --> L["Invalidate cache, surface OAuth error"]
```

### OAuth (Now: `HEAD`)

```mermaid
flowchart TD
    A["Claude OAuth fetch starts"] --> B["hasCachedCredentials?"]
    B --> C{"cached/refreshable creds exist?"}
    C -- "yes" --> D["loadWithAutoRefresh(allowKeychainPrompt=false unless bootstrap needed)"]
    C -- "no" --> E["Gate: should allow keychain prompt now?"]
    E --> F["loadWithAutoRefresh(allowKeychainPrompt=true if gate allows)"]
    D --> G{"expired?"}
    F --> G
    G -- "no" --> H["Use access token directly"]
    G -- "yes" --> I["POST /v1/oauth/token refresh_token grant"]
    I --> J{"Refresh status"}
    J -- "200" --> K["Save refreshed creds to CodexBar keychain cache + memory"]
    J -- "400/401 + invalid_grant" --> L["Record terminal auth failure; block refresh until auth fingerprint changes"]
    J -- "400/401 other" --> M["Record transient failure; exponential backoff"]
    K --> H
    H --> N{"has user:profile scope?"}
    N -- "no" --> O["Fail: missing scope"]
    N -- "yes" --> P["GET /api/oauth/usage with Bearer access token"]
    P --> Q["Map usage response -> snapshot"]
```

### OAuth Token Source Resolution (Now)

```mermaid
flowchart TD
    A["load(...)"] --> B["Environment token (CODEXBAR_CLAUDE_OAUTH_TOKEN)"]
    B -->|miss| C["Memory cache (valid + unexpired)"]
    C -->|miss| D["CodexBar keychain cache: com.steipete.codexbar.cache/oauth.claude"]
    D -->|miss| E["~/.claude/.credentials.json"]
    E -->|miss| F{"allowKeychainPrompt && prompt gate open?"}
    F -- "yes" --> G["Claude keychain service: Claude Code-credentials (promptable fallback)"]
    F -- "no" --> H["Stop without keychain prompt path"]
```

## Web Flow

### Web (Before and Now: core fetch path largely unchanged)

```mermaid
flowchart TD
    A["Claude Web fetch starts"] --> B{"manual cookie header configured?"}
    B -- "yes" --> C["Extract sessionKey from manual header"]
    B -- "no" --> D["Enumerate cookie import candidates"]
    D --> E["Try browser cookie import (claude.ai domain)"]
    E --> F{"sessionKey found?"}
    C --> F
    F -- "no" --> G["Fail: noSessionKeyFound"]
    F -- "yes" --> H["GET organizations + usage endpoints"]
    H --> I["Build web usage snapshot + identity + optional cost extras"]
```

### Web Candidate Filtering + Prompt Implications (Now)

```mermaid
flowchart TD
    A["cookieImportCandidates(...)"] --> B{"browser uses keychain for decryption?"}
    B -- "no (Safari/Firefox/Zen)" --> C["Keep candidate even if keychain disabled"]
    B -- "yes (Chromium family)" --> D{"Keychain disabled?"}
    D -- "yes" --> E["Drop candidate"]
    D -- "no" --> F["Check BrowserCookieAccessGate cooldown"]
    F --> G{"cooldown active?"}
    G -- "yes" --> E
    G -- "no" --> H["Attempt import; on access denied record 6h cooldown"]
```

## CLI Flow

### CLI Fetch Path (Provider Runtime = `.cli`)

```mermaid
flowchart TD
    A["CLI command (provider=claude)"] --> B{"source mode"}
    B -- "auto" --> C["Strategy order: web -> cli"]
    B -- "web" --> D["Web strategy only"]
    B -- "cli" --> E["CLI PTY strategy only"]
    B -- "oauth" --> F["OAuth strategy only"]
    E --> G["ClaudeStatusProbe via PTY session"]
    G --> H["Parse /usage output -> snapshot"]
```

Notes:
- The PTY parsing path itself is functionally stable in this range.
- Claude CLI session environment still scrubs OAuth env overrides and `ANTHROPIC_*` vars before launching the subprocess.

## App Runtime Auto Pipeline (Provider Fetch Plan)

### App Auto Strategy Order (Descriptor Pipeline)

```mermaid
flowchart TD
    A["App runtime source=auto"] --> B["Try OAuth strategy first"]
    B -->|"available + success"| Z["Done"]
    B -->|"unavailable or fallback on error"| C["Try Web strategy"]
    C -->|"success"| Z
    C -->|"unavailable or fallback on error"| D["Try CLI strategy"]
    D -->|"success"| Z
    D -->|"fail"| E["Provider fetch failure"]
```

### Internal `ClaudeUsageFetcher(dataSource:.auto)` Heuristic (Now)

```mermaid
flowchart TD
    A["ClaudeUsageFetcher.loadLatestUsage(dataSource=.auto)"] --> B["Probe OAuth creds non-interactively"]
    B --> C{"usable OAuth creds?"}
    C -- "yes" --> D["Use OAuth path"]
    C -- "no" --> E{"has web session?"}
    E -- "yes" --> F["Use Web path"]
    E -- "no" --> G{"claude binary present?"}
    G -- "yes" --> H["Try CLI PTY, if fails then OAuth"]
    G -- "no" --> I["Fallback to OAuth"]
```

## Keychain Prompt / Re-prompt Behavior

### Claude OAuth Keychain Prompt Gate (Now)

```mermaid
stateDiagram-v2
    [*] --> PromptAllowed
    PromptAllowed --> CooldownBlocked: recordDenied()
    CooldownBlocked --> CooldownBlocked: shouldAllowPrompt() before deniedUntil
    CooldownBlocked --> PromptAllowed: time >= deniedUntil (6h)
```

### OAuth Refresh Failure Gate (Now)

```mermaid
stateDiagram-v2
    [*] --> Open
    Open --> TransientBackoff: recordTransientFailure (400/401 non-invalid_grant)
    TransientBackoff --> Open: cooldown expires
    TransientBackoff --> Open: auth fingerprint changes
    Open --> TerminalBlocked: recordTerminalAuthFailure (invalid_grant)
    TerminalBlocked --> TerminalBlocked: unchanged auth fingerprint
    TerminalBlocked --> Open: auth fingerprint changes
    TransientBackoff --> TerminalBlocked: terminal auth failure observed
    TerminalBlocked --> Open: recordSuccess
```

## Key Differences in Permission Prompt Surfaces

- Before:
  - OAuth availability/load paths could reach Claude keychain access with less strict non-interactive protection.
  - No dedicated Claude OAuth prompt cooldown gate.
  - No terminal-vs-transient refresh failure gate.
- Now:
  - Non-interactive probes are stricter and reused broadly.
  - Promptable Claude keychain access is gated and usually only bootstrap-oriented.
  - Denials cause cooldown suppression to avoid repeated prompts.
  - Refresh failures can suppress repeated attempts until either timeout (transient) or credential change (terminal).

## Related Files

- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainAccessGate.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthRefreshFailureGate.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/KeychainAccessPreflight.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/KeychainNoUIQuery.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/BrowserCookieImportOrder.swift`
- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/BrowserCookieAccessGate.swift`
