Agent-readable wiki

CodexBar Hidden Quirks Wiki

CodexBar is a macOS menu-bar app that aggregates AI-provider quota and cost data from 40+ providers. This wiki surfaces non-obvious implementation constraints, safety rails, edge-case adapters, and behavioral decisions that a casual reader of the README would miss entirely.

Pages

  1. Hidden Quirks MapThe six non-obvious implementation quirks every maintainer should know: keychain accessibility migration, browser-cookie prompt suppression, a universal quota parser, Claude peak-hour awareness, a dual-path display link, and an orphan-detecting watchdog process. Each exists because a naive implementation either triggers repeated macOS permission dialogs, silently fails on diverse API shapes, or leaks child processes at app quit.
  2. Keychain Accessibility Migration — Silencing Rebuild PromptsOn every development rebuild macOS pops a keychain Allow/Deny dialog for each stored credential, breaking the edit-run loop. CodexBar silently migrates all legacy keychain items from their default accessibility level to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly on first launch. The migration is one-shot (guarded by UserDefaults key "KeychainMigrationV1Completed"), uses a delete-then-add dance because SecItemUpdate cannot change kSecAttrAccessible, and is skipped entirely when KeychainAccessGate.isDisabled is set (CI / sandboxed environments). The list of migrated accounts is hard-coded in KeychainMigration.itemsToMigrate and includes every cookie and token the app manages.
  3. BrowserCookieAccessGate — 6-Hour Prompt CooldownReading Chromium browser cookies for providers like Claude-web requires decrypting the "Chrome Safe Storage" keychain entry, which itself triggers a macOS permission prompt. BrowserCookieAccessGate wraps every cookie-fetch call and suppresses attempts for 6 hours after a denial. The gate uses KeychainAccessPreflight.checkGenericPassword to probe each known safe-storage label without triggering the full dialog; if interaction is required it records a "denied until" timestamp in UserDefaults and returns false immediately. The cooldown is intentionally long (6h) to prevent repeated nagging if the user clicks Deny. The gate is a no-op stub on non-macOS platforms so the same CodexBarCore code compiles for Linux tests. Evidence: BrowserCookieAccessGate.swift, BrowserCookieAccessGate.cooldownInterval = 60*60*6.
  4. Synthetic Provider — Universal Quota Parser That Handles Any API ShapeThe Synthetic provider is designed for any OpenAI-compatible proxy; its quota API can return wildly different JSON shapes. SyntheticUsageParser does not rely on a fixed schema: it tries 10+ key aliases for each field (percentUsed/usagePercent/used_percent/percent_used/percent…), auto-detects whether a percent value is already 0-100 or 0-1, derives missing fields by algebra (used = limit − remaining), and parses duration strings like "5hr", "30min", "2 days" by sorting suffix strings longest-first to prevent prefix collisions. When the known Synthetic API shape is detected (rollingFiveHourLimit / weeklyTokenLimit / search.hourly keys) it uses slot-positioned mapping so a missing middle lane stays nil instead of shifting labels. Timestamp heuristic: numbers > 1e12 are treated as milliseconds, numbers > 1e9 as seconds, strings are tried with and without fractional seconds. Evidence: SyntheticUsageParser.swift, SyntheticUsageParser.windowSuffixMultipliers sorted by suffix length.
  5. Claude Peak-Hour Awareness — ET 8am–2pm WeekdaysClaudePeakHours hard-codes Anthropic's known rate-limit peak window: weekdays 08:00–14:00 America/New_York. The status() method returns a labeled Status struct with a countdown ("Peak · ends in 2h 15m" or "Off-peak · peak in 18h 30m") computed against the Eastern timezone calendar. Weekends are always off-peak. The next-peak search correctly skips Saturday (skip=2 days) and Sunday (skip=1 day) so Monday is always the next target. ClaudeSourcePlanner uses this signal alongside credential availability and ProviderRuntime to decide which data source to attempt, making the fetch strategy time-aware without any external configuration. Evidence: ClaudePeakHours.swift peakStartHour=8, peakEndHour=14, peakTimeZone="America/New_York"; ClaudeSourcePlannerTests.swift.
  6. DisplayLink Dual-Path & Watchdog Orphan DetectionTwo unrelated but equally subtle runtime quirks shape how CodexBar animates its icon and manages child processes. DisplayLinkDriver uses NSScreen.displayLink (macOS 15+) when available and falls back to CVDisplayLink (macOS 14), bridging the two via a scheduleTick() dispatch to the main actor since CVDisplayLink fires on a background thread. CADisplayLink ticks are rate-limited by a manual timestamp comparison so the 12 fps target is honored regardless of the display's native refresh rate. Separately, CodexBarClaudeWatchdog is a standalone POSIX executable that spawns claude as a child process group leader and polls waitpid every 200ms. Its critical invariant: if getppid() returns 1, the macOS app has been killed without sending SIGTERM, so the watchdog kills the child process tree (SIGTERM → 500ms grace → SIGKILL) and exits. The watchdog also manually decodes waitpid's raw status integer because Swift cannot import function-like C macros (WIFEXITED/WEXITSTATUS). Evidence: DisplayLink.swift startCVDisplayLink(), CodexBarClaudeWatchdog/main.swift getppid()==1 branch, exitCode(fromWaitStatus:) bit-manipulation.

Complete Markdown

# CodexBar Hidden Quirks Wiki

> CodexBar is a macOS menu-bar app that aggregates AI-provider quota and cost data from 40+ providers. This wiki surfaces non-obvious implementation constraints, safety rails, edge-case adapters, and behavioral decisions that a casual reader of the README would miss entirely.

## Context Links

- [Agent index](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492)
- [GitHub repository](https://github.com/steipete/CodexBar)

## Repository Metadata

- Repository: steipete/CodexBar

- Generated: 2026-05-18T21:31:14.234Z
- Updated: 2026-05-21T21:34:24.914Z
- Runtime: Claude Code
- Format: Hidden Quirks
- Pages: 6

## Page Index

- 01. [Hidden Quirks Map](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/01-hidden-quirks-map.md) - The six non-obvious implementation quirks every maintainer should know: keychain accessibility migration, browser-cookie prompt suppression, a universal quota parser, Claude peak-hour awareness, a dual-path display link, and an orphan-detecting watchdog process. Each exists because a naive implementation either triggers repeated macOS permission dialogs, silently fails on diverse API shapes, or leaks child processes at app quit.
- 02. [Keychain Accessibility Migration — Silencing Rebuild Prompts](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/02-keychain-accessibility-migration-silencing-rebuild-prompts.md) - On every development rebuild macOS pops a keychain Allow/Deny dialog for each stored credential, breaking the edit-run loop. CodexBar silently migrates all legacy keychain items from their default accessibility level to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly on first launch. The migration is one-shot (guarded by UserDefaults key "KeychainMigrationV1Completed"), uses a delete-then-add dance because SecItemUpdate cannot change kSecAttrAccessible, and is skipped entirely when KeychainAccessGate.isDisabled is set (CI / sandboxed environments). The list of migrated accounts is hard-coded in KeychainMigration.itemsToMigrate and includes every cookie and token the app manages.
- 03. [BrowserCookieAccessGate — 6-Hour Prompt Cooldown](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/03-browsercookieaccessgate-6-hour-prompt-cooldown.md) - Reading Chromium browser cookies for providers like Claude-web requires decrypting the "Chrome Safe Storage" keychain entry, which itself triggers a macOS permission prompt. BrowserCookieAccessGate wraps every cookie-fetch call and suppresses attempts for 6 hours after a denial. The gate uses KeychainAccessPreflight.checkGenericPassword to probe each known safe-storage label without triggering the full dialog; if interaction is required it records a "denied until" timestamp in UserDefaults and returns false immediately. The cooldown is intentionally long (6h) to prevent repeated nagging if the user clicks Deny. The gate is a no-op stub on non-macOS platforms so the same CodexBarCore code compiles for Linux tests. Evidence: BrowserCookieAccessGate.swift, BrowserCookieAccessGate.cooldownInterval = 60*60*6.
- 04. [Synthetic Provider — Universal Quota Parser That Handles Any API Shape](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/04-synthetic-provider-universal-quota-parser-that-handles-any-api-shape.md) - The Synthetic provider is designed for any OpenAI-compatible proxy; its quota API can return wildly different JSON shapes. SyntheticUsageParser does not rely on a fixed schema: it tries 10+ key aliases for each field (percentUsed/usagePercent/used_percent/percent_used/percent…), auto-detects whether a percent value is already 0-100 or 0-1, derives missing fields by algebra (used = limit − remaining), and parses duration strings like "5hr", "30min", "2 days" by sorting suffix strings longest-first to prevent prefix collisions. When the known Synthetic API shape is detected (rollingFiveHourLimit / weeklyTokenLimit / search.hourly keys) it uses slot-positioned mapping so a missing middle lane stays nil instead of shifting labels. Timestamp heuristic: numbers > 1e12 are treated as milliseconds, numbers > 1e9 as seconds, strings are tried with and without fractional seconds. Evidence: SyntheticUsageParser.swift, SyntheticUsageParser.windowSuffixMultipliers sorted by suffix length.
- 05. [Claude Peak-Hour Awareness — ET 8am–2pm Weekdays](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/05-claude-peak-hour-awareness-et-8am-2pm-weekdays.md) - ClaudePeakHours hard-codes Anthropic's known rate-limit peak window: weekdays 08:00–14:00 America/New_York. The status() method returns a labeled Status struct with a countdown ("Peak · ends in 2h 15m" or "Off-peak · peak in 18h 30m") computed against the Eastern timezone calendar. Weekends are always off-peak. The next-peak search correctly skips Saturday (skip=2 days) and Sunday (skip=1 day) so Monday is always the next target. ClaudeSourcePlanner uses this signal alongside credential availability and ProviderRuntime to decide which data source to attempt, making the fetch strategy time-aware without any external configuration. Evidence: ClaudePeakHours.swift peakStartHour=8, peakEndHour=14, peakTimeZone="America/New_York"; ClaudeSourcePlannerTests.swift.
- 06. [DisplayLink Dual-Path & Watchdog Orphan Detection](https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/06-displaylink-dual-path-watchdog-orphan-detection.md) - Two unrelated but equally subtle runtime quirks shape how CodexBar animates its icon and manages child processes. DisplayLinkDriver uses NSScreen.displayLink (macOS 15+) when available and falls back to CVDisplayLink (macOS 14), bridging the two via a scheduleTick() dispatch to the main actor since CVDisplayLink fires on a background thread. CADisplayLink ticks are rate-limited by a manual timestamp comparison so the 12 fps target is honored regardless of the display's native refresh rate. Separately, CodexBarClaudeWatchdog is a standalone POSIX executable that spawns claude as a child process group leader and polls waitpid every 200ms. Its critical invariant: if getppid() returns 1, the macOS app has been killed without sending SIGTERM, so the watchdog kills the child process tree (SIGTERM → 500ms grace → SIGKILL) and exits. The watchdog also manually decodes waitpid's raw status integer because Swift cannot import function-like C macros (WIFEXITED/WEXITSTATUS). Evidence: DisplayLink.swift startCVDisplayLink(), CodexBarClaudeWatchdog/main.swift getppid()==1 branch, exitCode(fromWaitStatus:) bit-manipulation.

## Source File Index

- `Sources/CodexBar/DisplayLink.swift`
- `Sources/CodexBar/KeychainMigration.swift`
- `Sources/CodexBarClaudeWatchdog/main.swift`
- `Sources/CodexBarCore/BrowserCookieAccessGate.swift`
- `Sources/CodexBarCore/BrowserCookieImportOrder.swift`
- `Sources/CodexBarCore/Host/Process/SubprocessRunner.swift`
- `Sources/CodexBarCore/Host/PTY/TTYCommandRunner.swift`
- `Sources/CodexBarCore/KeychainAccessGate.swift`
- `Sources/CodexBarCore/KeychainAccessPreflight.swift`
- `Sources/CodexBarCore/KeychainNoUIQuery.swift`
- `Sources/CodexBarCore/Providers/Claude/ClaudeCredentialRouting.swift`
- `Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift`
- `Sources/CodexBarCore/Providers/Claude/ClaudeSourcePlanner.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageFetcher.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageParser.swift`
- `Tests/CodexBarTests/ClaudeOAuthCredentialsStoreTests.swift`
- `Tests/CodexBarTests/ClaudeSourcePlannerTests.swift`
- `Tests/CodexBarTests/OpenAIWebAccountSwitchTests.swift`
- `Tests/CodexBarTests/TTYCommandRunnerTests.swift`
- `Tests/CodexBarTests/VeniceUsageFetcherTests.swift`

---

## 01. Hidden Quirks Map

> The six non-obvious implementation quirks every maintainer should know: keychain accessibility migration, browser-cookie prompt suppression, a universal quota parser, Claude peak-hour awareness, a dual-path display link, and an orphan-detecting watchdog process. Each exists because a naive implementation either triggers repeated macOS permission dialogs, silently fails on diverse API shapes, or leaks child processes at app quit.

- Page Markdown: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/01-hidden-quirks-map.md
- Generated: 2026-05-18T21:29:22.463Z

### Source Files

- `Sources/CodexBar/KeychainMigration.swift`
- `Sources/CodexBarCore/BrowserCookieAccessGate.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageParser.swift`
- `Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift`
- `Sources/CodexBar/DisplayLink.swift`
- `Sources/CodexBarClaudeWatchdog/main.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [Sources/CodexBar/KeychainMigration.swift](Sources/CodexBar/KeychainMigration.swift)
- [Sources/CodexBarCore/BrowserCookieAccessGate.swift](Sources/CodexBarCore/BrowserCookieAccessGate.swift)
- [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift](Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift)
- [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift](Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift)
- [Sources/CodexBar/DisplayLink.swift](Sources/CodexBar/DisplayLink.swift)
- [Sources/CodexBarClaudeWatchdog/main.swift](Sources/CodexBarClaudeWatchdog/main.swift)
</details>

# Hidden Quirks Map

CodexBar is a macOS menu-bar app that monitors AI coding-assistant quotas across multiple providers. Its implementation touches several system surfaces—Keychain, browser cookies, display hardware, OS signals—where the obvious first approach silently fails or produces user-hostile behavior. This page documents six non-obvious implementation decisions that exist because naive alternatives either fire repeated macOS permission dialogs, fail to parse the wild diversity of real provider API responses, or leave orphan processes running after the app quits.

Each section explains the concrete problem the quirk solves, where the code lives, and what would break if it were removed.

---

## 1. Keychain Accessibility Migration

### The Problem

macOS requires apps to re-authenticate every time they access a Keychain item stored with the default `kSecAttrAccessibleWhenUnlocked` policy in certain development scenarios—specifically after code-signing changes or rebuilds. During active development this generates a blocking permission dialog for every stored token on every relaunch, which is unusable.

### The Solution

`KeychainMigration` runs once per installation and rewrites every stored credential to use `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`. This policy allows access after the first post-boot unlock, with no per-access prompt, while still binding the items to the device (preventing iCloud Keychain sync of raw tokens).

The migration is gated by a `UserDefaults` flag (`KeychainMigrationV1Completed`) so it runs exactly once:

```swift
// Sources/CodexBar/KeychainMigration.swift:41-59
if !UserDefaults.standard.bool(forKey: self.migrationKey) {
    // migrate each item: read → delete → re-add with new accessibility
    UserDefaults.standard.set(true, forKey: self.migrationKey)
}
```

The actual migration for each item is a three-step atomic dance: read the existing data and attributes, delete the old item, then write it back with the new accessibility constant. If any step fails the error is logged but the flag is still set—a partial migration is better than an infinite retry loop that keeps prompting users.

| Item migrated | Keychain account key |
|---|---|
| OpenAI (Codex) cookie | `codex-cookie` |
| Claude cookie | `claude-cookie` |
| Cursor cookie | `cursor-cookie` |
| Factory cookie | `factory-cookie` |
| MiniMax cookie + API token | `minimax-cookie`, `minimax-api-token` |
| Augment cookie | `augment-cookie` |
| Copilot API token | `copilot-api-token` |
| Zai API token | `zai-api-token` |
| Synthetic API key | `synthetic-api-key` |

Sources: [Sources/CodexBar/KeychainMigration.swift:5-63]()

---

## 2. Browser-Cookie Prompt Suppression Gate

### The Problem

Reading browser cookies to fetch session tokens requires decrypting them with the browser's "Safe Storage" key, which lives in the macOS Keychain. On Chrome/Chromium-family browsers, if the Keychain is locked or the app lacks prior access, a system dialog asks the user to approve. CodexBar polls on a background schedule; a prompt popping up every few minutes is intolerable.

### The Solution

`BrowserCookieAccessGate` acts as a per-browser circuit breaker. Before any cookie read it runs a preflight Keychain check (`KeychainAccessPreflight.checkGenericPassword`) that probes the Safe Storage entry without triggering a prompt. If the result is `.interactionRequired`, the gate records a `deniedUntil` timestamp 6 hours in the future and blocks all cookie access for that browser until the timer expires:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:15-51
private static let cooldownInterval: TimeInterval = 60 * 60 * 6  // 6 hours

public static func shouldAttempt(_ browser: Browser, now: Date = Date()) -> Bool {
    guard browser.usesKeychainForCookieDecryption else { return true }
    // … preflight check; if interactionRequired → block for 6h
}
```

The blocked-until timestamps are persisted in `UserDefaults` so they survive app restarts. The gate also reacts to actual `BrowserCookieError.accessDenied` errors via `recordIfNeeded`, recording a fresh 6-hour cooldown so a transient denial doesn't trigger retries.

Only browsers that actually use Keychain for cookie decryption (Chromium family) are subject to the gate; Safari and others pass through unconditionally (`guard browser.usesKeychainForCookieDecryption else { return true }`).

```mermaid
flowchart TD
    A[Refresh tick] --> B{usesKeychainForCookieDecryption?}
    B -- no --> C[Proceed]
    B -- yes --> D{Blocked until timestamp\nin UserDefaults?}
    D -- still future --> E[Skip – no prompt]
    D -- expired or absent --> F[Preflight Keychain probe]
    F -- allowed --> C
    F -- interactionRequired --> G[Record 6h cooldown\nSkip – no prompt]
```

Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:1-113]()

---

## 3. Universal Quota Parser

### The Problem

The "Synthetic" provider is a generic AI API aggregator whose response schema is undocumented and has varied across API versions and plan types. Different plan tiers return quota data under different key names, in different nesting shapes, and with percentage values that may be 0–1 floats or 0–100 integers.

### The Solution

`SyntheticUsageParser` (embedded in `SyntheticUsageStats.swift`) is a deliberately over-engineered fuzzy parser. It handles multiple strategies in order of preference:

**1. Slot-positional fast path.** If the response contains any of the known Synthetic-specific keys (`rollingFiveHourLimit`, `weeklyTokenLimit`, `search.hourly`), the parser extracts them into fixed UI slots `[slot-0: rolling-5h, slot-1: weekly, slot-2: search-hourly]`. A missing slot stays `nil` rather than promoting the next entry into the wrong UI label:

```swift
// Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:192-201
let slots: [[String: Any]?] = [rolling, weekly, searchHourly]
return slots.contains(where: { $0 != nil }) ? slots : nil
```

**2. Fuzzy fallback.** If no known keys are found, the parser walks 13 possible root-level or `data`-nested key names (`quotas`, `quota`, `limits`, `usage`, `entries`, `subscription`, `data`, and their `data`-nested counterparts), recursively descending into arrays and dicts to find objects that look like quota payloads.

**3. Per-field aliasing.** Each field within a quota object is resolved through an alias list. For example `usedPercent` is tried as any of: `percentUsed`, `usedPercent`, `usagePercent`, `usage_percent`, `used_percent`, `percent_used`, `percent`. Percentage values ≤ 1.0 are auto-scaled to 0–100:

```swift
// line 454-458
private static func normalizedPercent(_ value: Double?) -> Double? {
    guard let value else { return nil }
    if value <= 1 { return value * 100 }
    return value
}
```

**4. Time-window parsing.** The reset-window duration can arrive as an integer minutes/hours/days/seconds field, or as a free-text string like `"5hr"`, `"30min"`, `"2 days"`. Suffix matching is sorted longest-first so `"minutes"` always beats `"m"`:

```swift
// line 378-387
private static let windowSuffixMultipliers: [(suffix: String, multiplier: Double)] = {
    let raw: [(String, Double)] = [
        ("minutes", 1), ("minute", 1), ("mins", 1), ("min", 1), ("m", 1),
        ("hours", 60), …
    ]
    return raw.sorted { $0.0.count > $1.0.count } …
}()
```

**5. Reset-time vs. reset-description split.** When a `resetsAt` timestamp is available, `resetDescription` is explicitly left `nil` so the UI recalculates a live countdown on every render rather than freezing a stale "in Xm" string at parse time.

**6. Date value heuristics.** Timestamps may arrive as Unix seconds, Unix milliseconds, or ISO-8601 strings. The parser distinguishes seconds from milliseconds by checking whether the numeric value exceeds `1_000_000_000_000`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:154-708]()

---

## 4. Claude Peak-Hour Awareness

### The Problem

Claude (Anthropic's API and Pro plan) degrades response quality and increases latency during US business hours due to high demand. Surfacing this in the menu bar helps users plan around slowdowns—but the calculation must account for time zones, weekends, and countdown-to-next-peak correctly.

### The Solution

`ClaudePeakHours` hard-codes peak time as **08:00–14:00 Eastern Time, Monday–Friday**. All comparisons are done in a `Calendar` configured with `America/New_York` timezone, regardless of the user's local zone:

```swift
// Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:4-6
private static let peakTimeZone = TimeZone(identifier: "America/New_York")!
private static let peakStartHour = 8
private static let peakEndHour = 14
```

When inside peak, the label is `"Peak · ends in Xh Ym"`. When outside, `nextPeakStart(after:)` computes the next weekday 08:00 ET, advancing past Saturday (skip 1 day) and Sunday (skip 2 days):

```swift
// line 56-63
let skip = switch weekday {
case 1: 1   // Sunday → Monday
case 7: 2   // Saturday → Monday
default: 0
}
```

One subtle detail: before extracting hour/minute components the code snaps the input date to the start of its minute (`calendar.dateInterval(of: .minute, for: date)?.start`) so that a date at 13:59:59 doesn't drift into "peak" for a full minute more than expected.

Sources: [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:1-83]()

---

## 5. Dual-Path Display Link

### The Problem

SwiftUI needs a clock tick to drive live countdowns (quota resets, peak-hour timers) in a menu-bar popover. Using a simple `Timer` can miss beats when the main runloop is busy. Using `CVDisplayLink` directly on macOS 15+ is deprecated in favor of `NSScreen.displayLink`. But the app targets macOS 14, so both APIs must coexist.

### The Solution

`DisplayLinkDriver` picks the API at runtime:

- **macOS 15+**: creates an `NSScreen`-backed `CADisplayLink` with a locked `CAFrameRateRange` (min = max = preferred = target FPS), added to `.main` runloop in `.common` mode.
- **macOS 14**: falls back to `CVDisplayLink`, which fires on a non-main thread. The callback bridges back to the main actor via `Task { @MainActor [weak self] in self?.handleTick() }`.

Both paths share `handleTick()`, which applies a manual interval gate against `CACurrentMediaTime()` because neither API guarantees exactly the requested FPS—the gate ensures ticks are never fired faster than the configured interval:

```swift
// Sources/CodexBar/DisplayLink.swift:57-66
private func handleTick() {
    let now = CACurrentMediaTime()
    if self.lastTickTimestamp > 0, now - self.lastTickTimestamp < self.targetInterval {
        return
    }
    self.lastTickTimestamp = now
    self.tick &+= 1   // wrapping add; drives @Observable invalidation
    self.onTick?()
}
```

The `tick` counter uses `&+=` (wrapping addition) to avoid undefined behavior on overflow for a counter that is never read arithmetically—only used as an `@Observable` invalidation signal.

`deinit` dispatches `stop()` back onto the `@MainActor` because `CVDisplayLink` must be stopped from the same actor context it was started from, and `deinit` is actor-unaware.

Sources: [Sources/CodexBar/DisplayLink.swift:1-96]()

---

## 6. Orphan-Detecting Watchdog Process

### The Problem

CodexBar spawns a subprocess (the Claude CLI tool) to fetch usage data. If the parent app crashes, is force-quit, or is killed by `launchd` under memory pressure, the child process becomes an orphan: it keeps running, consuming memory and CPU, with no parent to wait for it. `atexit` handlers and `NotificationCenter` observers in the parent are not reliable across crash-induced terminations.

### The Solution

`CodexBarClaudeWatchdog` is a dedicated standalone binary that sits between CodexBar and the Claude CLI. CodexBar spawns the watchdog, the watchdog spawns the actual Claude process, and the watchdog poll-loops watching two conditions:

**1. Signal forwarding.** `SIGTERM`, `SIGINT`, and `SIGHUP` are caught and forwarded to the child process group:

```swift
// Sources/CodexBarClaudeWatchdog/main.swift:97-99
signal(SIGTERM, handleTerminationSignal)
signal(SIGINT, handleTerminationSignal)
signal(SIGHUP, handleTerminationSignal)
```

**2. Parent-death detection via `getppid() == 1`.** When a process's parent dies, `init` (PID 1) adopts it. The watchdog checks for this every 200 ms:

```swift
// Sources/CodexBarClaudeWatchdog/main.swift:115-120
if getppid() == 1 {
    terminateChild()
    _ = waitpid(globalChildPID, &status, 0)
    Darwin.exit(exitCode(fromWaitStatus: status))
}
```

Kill semantics are two-stage: `SIGTERM` to the process group (including any grandchildren spawned by the CLI), then a 500 ms grace period, then `SIGKILL` if still alive. The watchdog puts the child into its own process group immediately after spawn (`setpgid(globalChildPID, globalChildPID)`) so that the group kill can't accidentally catch the watchdog itself.

The exit-code translation hand-decodes `wait(2)` macros that Swift cannot import directly (they are C function-like macros): signal-killed processes exit as `128 + signal_number`, normal exits propagate the exit byte verbatim.

```mermaid
sequenceDiagram
    participant App as CodexBar
    participant WD as Watchdog
    participant CLI as Claude CLI

    App->>WD: posix_spawn(watchdog)
    WD->>CLI: posix_spawnp(claude-cli)
    WD->>WD: setpgid(childPID, childPID)
    loop every 200ms
        WD->>WD: waitpid(WNOHANG)
        WD->>WD: check getppid() == 1?
        WD->>WD: check globalShouldTerminate?
    end
    App--xWD: app crashes / killed
    Note over WD: getppid() becomes 1
    WD->>CLI: SIGTERM → process group
    WD->>CLI: SIGKILL (if still alive after 500ms)
    WD->>WD: exit
```

Sources: [Sources/CodexBarClaudeWatchdog/main.swift:1-122]()

---

## Summary

These six quirks form a defense-in-depth layer around the parts of CodexBar that touch system APIs. The keychain migration and browser cookie gate together prevent permission-dialog fatigue. The universal quota parser absorbs API schema drift without requiring code changes. The peak-hour clock converts a server-side load pattern into a user-visible, timezone-correct signal. The dual-path display link keeps live countdowns smooth across an OS-version boundary. And the watchdog ensures that spawned subprocesses never outlive the parent, regardless of how the parent terminates. Collectively they represent the kind of knowledge that exists only in commit history and code comments—this page is a map of that hidden terrain.

Sources: [Sources/CodexBarClaudeWatchdog/main.swift:85-122]()

---

## 02. Keychain Accessibility Migration — Silencing Rebuild Prompts

> On every development rebuild macOS pops a keychain Allow/Deny dialog for each stored credential, breaking the edit-run loop. CodexBar silently migrates all legacy keychain items from their default accessibility level to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly on first launch. The migration is one-shot (guarded by UserDefaults key "KeychainMigrationV1Completed"), uses a delete-then-add dance because SecItemUpdate cannot change kSecAttrAccessible, and is skipped entirely when KeychainAccessGate.isDisabled is set (CI / sandboxed environments). The list of migrated accounts is hard-coded in KeychainMigration.itemsToMigrate and includes every cookie and token the app manages.

- Page Markdown: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/02-keychain-accessibility-migration-silencing-rebuild-prompts.md
- Generated: 2026-05-18T21:29:05.263Z

### Source Files

- `Sources/CodexBar/KeychainMigration.swift`
- `Sources/CodexBarCore/KeychainAccessGate.swift`
- `Sources/CodexBarCore/KeychainNoUIQuery.swift`
- `Sources/CodexBarCore/KeychainAccessPreflight.swift`
- `Tests/CodexBarTests/ClaudeOAuthCredentialsStoreTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [Sources/CodexBar/KeychainMigration.swift](Sources/CodexBar/KeychainMigration.swift)
- [Sources/CodexBar/HiddenWindowView.swift](Sources/CodexBar/HiddenWindowView.swift)
- [Sources/CodexBarCore/KeychainAccessGate.swift](Sources/CodexBarCore/KeychainAccessGate.swift)
- [Sources/CodexBarCore/KeychainNoUIQuery.swift](Sources/CodexBarCore/KeychainNoUIQuery.swift)
- [Sources/CodexBarCore/KeychainAccessPreflight.swift](Sources/CodexBarCore/KeychainAccessPreflight.swift)
- [Tests/CodexBarTests/KeychainMigrationTests.swift](Tests/CodexBarTests/KeychainMigrationTests.swift)
- [Tests/CodexBarTests/ClaudeOAuthCredentialsStoreTests.swift](Tests/CodexBarTests/ClaudeOAuthCredentialsStoreTests.swift)
- [docs/KEYCHAIN_FIX.md](docs/KEYCHAIN_FIX.md)
</details>

# Keychain Accessibility Migration — Silencing Rebuild Prompts

During development, macOS prompts the user with an Allow/Deny dialog for every keychain item an app touches whenever the app's code signature changes — which happens on every `⌘R` rebuild. For a menu-bar app like CodexBar that stores a dozen provider cookies and tokens, this produces a blocking dialog storm before the app even renders its menu.

CodexBar solves this with a one-shot migration that runs on first launch after installation. It upgrades every legacy keychain item from the default accessibility level to `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`. Items using this level are bound to the device and readable without user interaction after the Mac has been unlocked once — and crucially, macOS does not re-prompt on subsequent code-signature changes for items at this level. The migration is idempotent, gated by a `UserDefaults` flag, skipped entirely in test and CI environments, and completed silently in a background task.

---

## Why the Default Accessibility Level Triggers Prompts

macOS Keychain uses access control lists (ACLs) tied to the app's code signature. Items stored with the default accessibility (`kSecAttrAccessibleWhenUnlocked`, no `ThisDeviceOnly` suffix) carry ACL entries that macOS re-evaluates on every signature change. Switching to `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` replaces that per-signature ACL with a device-scoped, session-open check — which the Security framework can satisfy without surfacing any UI.

The comment in the source makes this explicit:

```swift
// Sources/CodexBar/KeychainMigration.swift:5-6
/// Migrates keychain items to use kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
/// to prevent permission prompts on every rebuild during development.
```

---

## The Migration Entry Point

The migration is triggered from `HiddenWindowView`, a zero-size (`1×1 pt`) SwiftUI view used solely as a lifecycle anchor for the macOS app. Its `.task` modifier spawns a detached background task so the migration never blocks the main actor:

```swift
// Sources/CodexBar/HiddenWindowView.swift:14-18
.task {
    // Migrate keychain items to reduce permission prompts during development (runs off main thread)
    await Task.detached(priority: .userInitiated) {
        KeychainMigration.migrateIfNeeded()
    }.value
}
```

The `docs/KEYCHAIN_FIX.md` notes that an earlier revision ran this in `CodexBarApp.init()` — synchronously on the main thread. The current placement in a `.task`-launched detached task is deliberate: `SecItemCopyMatching`, `SecItemDelete`, and `SecItemAdd` can block on slow keychains and must not run on the main actor.

Sources: [Sources/CodexBar/HiddenWindowView.swift:14-18]()

---

## The Migration Algorithm

`KeychainMigration.migrateIfNeeded()` is a `static func` on an `enum` (used as a namespace). Its logic is:

```
migrateIfNeeded()
  ├─ KeychainAccessGate.isDisabled? → return (CI / test / manual disable)
  ├─ UserDefaults "KeychainMigrationV1Completed" == true? → return (already done)
  └─ for each item in itemsToMigrate:
       migrateItem(item)
         ├─ SecItemCopyMatching (read existing item + its data)
         │   ├─ errSecItemNotFound → return false (nothing to do)
         │   └─ errSecSuccess → extract data + current kSecAttrAccessible value
         ├─ already kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly? → return false
         ├─ SecItemDelete (remove old item)
         └─ SecItemAdd (re-add with new accessibility + same data)
     UserDefaults.set(true, "KeychainMigrationV1Completed")
```

The delete-then-add pattern is a hard requirement of the Security framework: `SecItemUpdate` cannot change `kSecAttrAccessible` on an existing item. Any attempt to update that attribute via `SecItemUpdate` returns `errSecParam`. The only way to change accessibility is to delete and recreate the item.

Sources: [Sources/CodexBar/KeychainMigration.swift:71-141]()

### Error Handling

Each item's migration is wrapped in a `do/catch` that increments `errorCount` but does not abort the loop. This means a single locked or corrupted item does not prevent the rest from migrating. The migration flag `KeychainMigrationV1Completed` is still set to `true` even when `errorCount > 0` — a deliberate trade-off: re-running a partial migration would risk double-deleting items written between runs.

```swift
// Sources/CodexBar/KeychainMigration.swift:48-55
do {
    if try self.migrateItem(item) {
        migratedCount += 1
    }
} catch {
    errorCount += 1
    self.log.error("Failed to migrate \(item.label): \(String(describing: error))")
}
```

Sources: [Sources/CodexBar/KeychainMigration.swift:44-59]()

---

## The Hard-Coded Item List

`KeychainMigration.itemsToMigrate` is a static `[MigrationItem]` array. Every entry specifies a `service` and an optional `account`. All current entries share the service `com.steipete.CodexBar`:

| Account | Kind |
|---|---|
| `codex-cookie` | Codex session cookie |
| `claude-cookie` | Claude web session cookie |
| `cursor-cookie` | Cursor session cookie |
| `factory-cookie` | Factory session cookie |
| `minimax-cookie` | MiniMax session cookie |
| `minimax-api-token` | MiniMax API token |
| `augment-cookie` | Augment session cookie |
| `copilot-api-token` | GitHub Copilot API token |
| `zai-api-token` | Zai API token |
| `synthetic-api-key` | Synthetic API key |

A test in `KeychainMigrationTests` enforces this list as a contract — any missing entry causes a test failure:

```swift
// Tests/CodexBarTests/KeychainMigrationTests.swift:6-23
@Test
func `migration list covers known keychain items`() {
    let items = Set(KeychainMigration.itemsToMigrate.map(\.label))
    let expected: Set = [
        "com.steipete.CodexBar:codex-cookie",
        // ...
    ]
    let missing = expected.subtracting(items)
    #expect(missing.isEmpty, "Missing migration entries: \(missing.sorted())")
}
```

**Why hard-coded?** The migration is a one-time repair for items already present on disk. A dynamic discovery approach would risk migrating unrelated items from the same service namespace; hard-coding makes the scope explicit and auditable.

Note from `docs/KEYCHAIN_FIX.md`: the migration covers only legacy `com.steipete.CodexBar` items. The Claude OAuth cache (service `com.steipete.codexbar.cache`) is written fresh with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` from the start and is not included in this migration list.

Sources: [Sources/CodexBar/KeychainMigration.swift:21-32](), [Tests/CodexBarTests/KeychainMigrationTests.swift:6-23]()

---

## The CI / Test Gate

`KeychainAccessGate.isDisabled` is checked as the very first guard in `migrateIfNeeded()`:

```swift
// Sources/CodexBar/KeychainMigration.swift:36-39
guard !KeychainAccessGate.isDisabled else {
    self.log.info("Keychain access disabled; skipping migration")
    return
}
```

`KeychainAccessGate` has several disable paths:

| Path | Mechanism |
|---|---|
| Running under Swift test runner (DEBUG only) | Process name `swiftpm-testing-helper` or `XCTestConfigurationFilePath` env var |
| `CODEXBAR_ALLOW_TEST_KEYCHAIN_ACCESS=1` env var | Opt-in override that re-enables the real keychain in tests |
| `UserDefaults "debugDisableKeychainAccess"` | Set by Preferences → Advanced → Disable Keychain access |
| App Group shared defaults with same key | For multi-process setups |
| `overrideValue` static property | Programmatic override (used in tests) |
| `@TaskLocal taskOverrideValue` | Per-task override for test isolation |

The `@TaskLocal` override (`withTaskOverrideForTesting`) is used extensively in `ClaudeOAuthCredentialsStoreTests` to isolate keychain behaviour per test without affecting global state:

```swift
// Tests/CodexBarTests/ClaudeOAuthCredentialsStoreTests.swift:30-32
try KeychainAccessGate.withTaskOverrideForTesting(false) {
    // keychain reads are live in this scope
```

Sources: [Sources/CodexBarCore/KeychainAccessGate.swift:11-46](), [Tests/CodexBarTests/ClaudeOAuthCredentialsStoreTests.swift:30-32]()

---

## Non-Interactive Query Infrastructure

The migration reads existing items without a `kSecUseAuthenticationContext` guard (it intentionally needs to read whatever is there, including items that may still prompt). But the rest of the keychain stack uses `KeychainNoUIQuery` to prevent any UI during normal reads:

```swift
// Sources/CodexBarCore/KeychainNoUIQuery.swift:11-18
static func apply(to query: inout [String: Any]) {
    let context = LAContext()
    context.interactionNotAllowed = true
    query[kSecUseAuthenticationContext as String] = context

    // Keep explicit UI-fail policy for legacy keychain behavior on macOS where
    // `interactionNotAllowed` alone can still surface Allow/Deny prompts.
    query[kSecUseAuthenticationUI as String] = self.uiFailPolicy as CFString
}
```

The `uiFailPolicy` constant (`kSecUseAuthenticationUIFail`) is resolved at runtime via `dlopen`/`dlsym` to avoid a deprecation warning at compile time — a localized workaround for the fact that Apple deprecated the constant in later SDKs while the underlying behavior is still necessary:

```swift
// Sources/CodexBarCore/KeychainNoUIQuery.swift:25-39
private static func resolveUIFailPolicy() -> String {
    let securityPath = "/System/Library/Frameworks/Security.framework/Security"
    guard let handle = dlopen(securityPath, RTLD_NOW) else {
        return "u_AuthUIF"  // hard-coded fallback value
    }
    // ...
    guard let symbol = dlsym(handle, "kSecUseAuthenticationUIFail") else {
        return "u_AuthUIF"
    }
```

The fallback string `"u_AuthUIF"` is the raw CFString value of `kSecUseAuthenticationUIFail`, used if the dynamic lookup fails. This ensures the no-UI behaviour is preserved even on future OS versions that might remove the symbol from the framework.

Sources: [Sources/CodexBarCore/KeychainNoUIQuery.swift:11-39]()

---

## Preflight Probing

`KeychainAccessPreflight.checkGenericPassword` is a companion mechanism used for post-migration reads. It probes whether an item is accessible without UI before committing to a full interactive load. It applies `KeychainNoUIQuery` and requests only attributes (never `kSecReturnData`), because requesting the secret payload was observed to trigger legacy prompts on some macOS configurations:

```swift
// Sources/CodexBarCore/KeychainAccessPreflight.swift:167-182
static func makeGenericPasswordPreflightQuery(service: String, account: String?) -> [String: Any] {
    var query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecMatchLimit as String: kSecMatchLimitOne,
        // Preflight should never trigger UI. Avoid requesting the secret payload (`kSecReturnData`) because
        // some macOS configurations have been observed to show the legacy keychain prompt unless the query
        // is strictly non-interactive.
        kSecReturnAttributes as String: true,
    ]
    KeychainNoUIQuery.apply(to: &query)
```

Possible outcomes are `.allowed`, `.interactionRequired`, `.notFound`, and `.failure(Int)`. When the preflight returns `.interactionRequired`, the app can choose to show a pre-alert explaining why the keychain prompt is about to appear, rather than letting macOS show a bare Allow/Deny dialog with no context.

Sources: [Sources/CodexBarCore/KeychainAccessPreflight.swift:126-184]()

---

## Verifying and Resetting the Migration

### Check whether migration has run

```bash
defaults read com.steipete.codexbar KeychainMigrationV1Completed
```

Returns `1` if completed, error if not yet set.

### Reset for local testing

```bash
defaults delete com.steipete.codexbar KeychainMigrationV1Completed
./Scripts/compile_and_run.sh
```

`KeychainMigration.resetMigrationFlag()` (line 144) exists for test-only resets and calls `UserDefaults.standard.removeObject(forKey: migrationKey)`.

### Inspect migration logs

```bash
log show --predicate 'subsystem == "com.steipete.codexbar" && category == "keychain-migration"' --last 10m
```

---

## Flow Summary

```mermaid
flowchart TD
    A[HiddenWindowView .task] -->|detached Task| B[KeychainMigration.migrateIfNeeded]
    B --> C{KeychainAccessGate.isDisabled?}
    C -->|yes| D[return — CI / test / disabled]
    C -->|no| E{UserDefaults\nKeychainMigrationV1Completed?}
    E -->|true| F[return — already done]
    E -->|false| G[Loop over itemsToMigrate]
    G --> H[SecItemCopyMatching\nread item + accessibility]
    H -->|not found| I[skip item]
    H -->|already correct| I
    H -->|needs migration| J[SecItemDelete]
    J --> K[SecItemAdd with\nAfterFirstUnlockThisDeviceOnly]
    K --> G
    G -->|loop done| L[UserDefaults set\nKeychainMigrationV1Completed = true]
```

The migration is the first line of defense against keychain dialogs; `KeychainNoUIQuery` and `KeychainAccessPreflight` form the second line that suppresses prompts on every subsequent read. Together they eliminate the edit-run dialog storm entirely for legacy items, while the Claude OAuth path retains a configurable prompt policy for the separate `Claude Code-credentials` keychain service it does not own. Sources: [docs/KEYCHAIN_FIX.md:32-38]()

---

## 03. BrowserCookieAccessGate — 6-Hour Prompt Cooldown

> Reading Chromium browser cookies for providers like Claude-web requires decrypting the "Chrome Safe Storage" keychain entry, which itself triggers a macOS permission prompt. BrowserCookieAccessGate wraps every cookie-fetch call and suppresses attempts for 6 hours after a denial. The gate uses KeychainAccessPreflight.checkGenericPassword to probe each known safe-storage label without triggering the full dialog; if interaction is required it records a "denied until" timestamp in UserDefaults and returns false immediately. The cooldown is intentionally long (6h) to prevent repeated nagging if the user clicks Deny. The gate is a no-op stub on non-macOS platforms so the same CodexBarCore code compiles for Linux tests. Evidence: BrowserCookieAccessGate.swift, BrowserCookieAccessGate.cooldownInterval = 60*60*6.

- Page Markdown: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/03-browsercookieaccessgate-6-hour-prompt-cooldown.md
- Generated: 2026-05-18T21:29:43.933Z

### Source Files

- `Sources/CodexBarCore/BrowserCookieAccessGate.swift`
- `Sources/CodexBarCore/BrowserCookieImportOrder.swift`
- `Sources/CodexBarCore/KeychainAccessPreflight.swift`
- `Sources/CodexBarCore/KeychainNoUIQuery.swift`
- `Tests/CodexBarTests/OpenAIWebAccountSwitchTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [Sources/CodexBarCore/BrowserCookieAccessGate.swift](Sources/CodexBarCore/BrowserCookieAccessGate.swift)
- [Sources/CodexBarCore/KeychainAccessPreflight.swift](Sources/CodexBarCore/KeychainAccessPreflight.swift)
- [Sources/CodexBarCore/KeychainNoUIQuery.swift](Sources/CodexBarCore/KeychainNoUIQuery.swift)
- [Sources/CodexBarCore/BrowserCookieImportOrder.swift](Sources/CodexBarCore/BrowserCookieImportOrder.swift)
- [Sources/CodexBarCore/KeychainAccessGate.swift](Sources/CodexBarCore/KeychainAccessGate.swift)
- [Tests/CodexBarTests/BrowserDetectionTests.swift](Tests/CodexBarTests/BrowserDetectionTests.swift)
- [Tests/CodexBarTests/AlibabaCodingPlanCookieImporterTests.swift](Tests/CodexBarTests/AlibabaCodingPlanCookieImporterTests.swift)
</details>

# BrowserCookieAccessGate — 6-Hour Prompt Cooldown

`BrowserCookieAccessGate` is a macOS-only guard that prevents repeated keychain permission prompts when CodexBar attempts to read Chromium-based browser cookies. Chromium stores its cookie encryption key in the macOS keychain under the "Chrome Safe Storage" label (and per-browser equivalents). Accessing that key triggers a native macOS Allow/Deny dialog — a disruptive, blocking UX event. If the user clicks Deny, the same dialog would immediately reappear on the next usage check. The gate solves this by recording a "denied until" timestamp in `UserDefaults` and skipping all cookie-fetch attempts for the next **six hours**.

On non-macOS platforms the entire type is replaced by a stub that unconditionally returns `true` for `shouldAttempt`, making the gate a zero-cost abstraction for Linux CI builds.

---

## Why Chromium Browsers Need Keychain Access

Not all browsers store cookies in the same way. Safari and Firefox do not use the keychain for cookie decryption, so their cookies can be read without any permission dialog. Chromium-based browsers (Chrome, Arc, Brave, Edge, Vivaldi, Dia, Yandex, Comet, and others) encrypt their cookies with a key stored in the macOS keychain.

The `usesKeychainForCookieDecryption` property on `Browser` makes this distinction explicit:

```swift
// Sources/CodexBarCore/BrowserCookieImportOrder.swift:35-55
var usesKeychainForCookieDecryption: Bool {
    switch self {
    case .safari, .firefox, .zen:
        return false
    case .chrome, .chromeBeta, .chromeCanary,
         .arc, .arcBeta, .arcCanary,
         .chatgptAtlas, .chromium,
         .brave, .braveBeta, .braveNightly,
         .edge, .edgeBeta, .edgeCanary,
         .helium, .vivaldi, .dia, .yandex, .comet:
        return true
    @unknown default:
        return true  // conservative default
    }
}
```

The `@unknown default: return true` is a deliberate conservative choice — any future Chromium-derived browser is assumed to need keychain access until proven otherwise. Sources: [Sources/CodexBarCore/BrowserCookieImportOrder.swift:35-55]()

---

## The `shouldAttempt` Decision Flow

The public API is a single static predicate: `BrowserCookieAccessGate.shouldAttempt(_ browser:)`. All cookie-fetch call sites must pass through it before touching the keychain.

```mermaid
flowchart TD
    A[shouldAttempt called] --> B{browser.usesKeychainForCookieDecryption?}
    B -- No --> C[return true]
    B -- Yes --> D{KeychainAccessGate.isDisabled?}
    D -- Yes --> E[return false]
    D -- No --> F{deniedUntil timestamp in state?}
    F -- Yes, still future --> G[return false]
    F -- No or expired --> H[chromiumKeychainRequiresInteraction?]
    H -- No --> I[return true]
    H -- Yes --> J[record deniedUntil = now + 6h, persist, return false]
```

The two-phase structure is subtle: the in-memory/persisted cooldown check comes **before** the live keychain probe. Only if no existing cooldown is active does the gate actually probe the keychain via `KeychainAccessPreflight`. This prevents redundant probes during the cooldown window.

Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:18-51]()

---

## Keychain Preflight: Probing Without Triggering the Dialog

The core of the no-UI probe is `KeychainAccessPreflight.checkGenericPassword(service:account:)`. It issues a `SecItemCopyMatching` query that requests only keychain **attributes** (not the secret data), and applies two complementary no-interaction flags:

1. **`LAContext.interactionNotAllowed = true`** — tells LocalAuthentication to abort rather than prompt.
2. **`kSecUseAuthenticationUI = kSecUseAuthenticationUIFail`** — applies the legacy Security framework flag for older macOS keychain behavior where the `LAContext` flag alone was insufficient to suppress the Allow/Deny prompt.

```swift
// Sources/CodexBarCore/KeychainNoUIQuery.swift:13-18
static func apply(to query: inout [String: Any]) {
    let context = LAContext()
    context.interactionNotAllowed = true
    query[kSecUseAuthenticationContext as String] = context
    query[kSecUseAuthenticationUI as String] = self.uiFailPolicy as CFString
}
```

The `kSecUseAuthenticationUIFail` symbol is deprecated in modern SDKs. `KeychainNoUIQuery` resolves it at runtime via `dlopen`/`dlsym` to avoid a compile-time deprecation warning while still applying the correct constant value:

```swift
// Sources/CodexBarCore/KeychainNoUIQuery.swift:26-38
private static func resolveUIFailPolicy() -> String {
    let securityPath = "/System/Library/Frameworks/Security.framework/Security"
    guard let handle = dlopen(securityPath, RTLD_NOW) else { return "u_AuthUIF" }
    defer { dlclose(handle) }
    guard let symbol = dlsym(handle, "kSecUseAuthenticationUIFail") else { return "u_AuthUIF" }
    let valuePointer = symbol.assumingMemoryBound(to: CFString?.self)
    return (valuePointer.pointee as String?) ?? "u_AuthUIF"
}
```

The fallback literal `"u_AuthUIF"` is the raw string value of the constant, used if dynamic resolution fails. Sources: [Sources/CodexBarCore/KeychainNoUIQuery.swift:9-39]()

### Preflight Outcomes

| Outcome | Meaning | Gate decision |
|---|---|---|
| `.allowed` | Keychain item readable without UI | Return `true` immediately (short-circuit loop) |
| `.interactionRequired` | Would show a dialog | Record cooldown, return `false` |
| `.notFound` | No matching keychain item | Continue to next label |
| `.failure(Int)` | Other Security error | Continue to next label |

Note: `.allowed` returns `false` for `chromiumKeychainRequiresInteraction()` (meaning interaction is *not* required), making the naming convention a double-negative to watch for. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:84-96]()

---

## The Safe-Storage Label List

`BrowserCookieAccessGate` probes every known "Chrome Safe Storage" keychain service name across all supported Chromium browsers. The label list is defined on `Browser` itself (in `SweetCookieKit`) and mirrored into the gate:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:98
private static let safeStorageLabels: [(service: String, account: String)] = Browser.safeStorageLabels
```

Iterating all labels matters because Chrome, Brave, Arc, and others each register distinct keychain service names. If *any* label probes as `.allowed`, the gate concludes that the keychain is accessible without UI and allows the fetch. If *any* probes as `.interactionRequired`, it immediately trips the cooldown. Labels that come back `.notFound` are silently skipped — the browser simply isn't installed. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:84-98]()

---

## State Management and Persistence

The gate uses an `OSAllocatedUnfairLock` to protect a small `State` struct containing the per-browser denial map:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:8-13
private struct State {
    var loaded = false
    var deniedUntilByBrowser: [String: Date] = [:]
}
private static let lock = OSAllocatedUnfairLock<State>(initialState: State())
```

State is lazily loaded from `UserDefaults` on the first access (`loadIfNeeded`). Persistence serializes `Date` values as `Double` (Unix timestamps) under the key `"browserCookieAccessDeniedUntil"`:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:109-112
private static func persist(_ state: State) {
    let raw = state.deniedUntilByBrowser.mapValues { $0.timeIntervalSince1970 }
    UserDefaults.standard.set(raw, forKey: self.defaultsKey)
}
```

This means the cooldown survives application restarts. A user who clicks Deny will not be re-prompted for six hours even after quitting and relaunching CodexBar. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:100-112]()

### Cooldown Expiry

When `shouldAttempt` is called and finds a stored timestamp that is already in the past, it removes the entry and re-runs the live keychain probe:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:23-31
if let blockedUntil = state.deniedUntilByBrowser[browser.rawValue] {
    if blockedUntil > now {
        return false  // still cooling down
    }
    state.deniedUntilByBrowser.removeValue(forKey: browser.rawValue)
    self.persist(state)
}
return true  // proceed to live probe
```

The expired entry is removed and persisted before the live probe runs — a clean state transition. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:23-34]()

---

## Two Entry Points for Recording Denials

Beyond the proactive preflight in `shouldAttempt`, there is a reactive path for recording denials that slip through:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:53-74
public static func recordIfNeeded(_ error: Error, now: Date = Date()) {
    guard let error = error as? BrowserCookieError else { return }
    guard case .accessDenied = error else { return }
    self.recordDenied(for: error.browser, now: now)
}

public static func recordDenied(for browser: Browser, now: Date = Date()) { ... }
```

Call sites that receive a `BrowserCookieError.accessDenied` (i.e., the actual cookie read failed with an access error) can call `recordIfNeeded` to start the cooldown retroactively. This handles the edge case where the preflight passed but the full keychain fetch was denied at a different layer. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:53-74]()

---

## Integration into `BrowserCookieClient`

The gate wraps `SweetCookieKit`'s `BrowserCookieClient` via an extension that adds a `codexBarRecords` method:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:115-124
extension BrowserCookieClient {
    public func codexBarRecords(
        matching query: BrowserCookieQuery,
        in browser: Browser,
        logger: ((String) -> Void)? = nil) throws -> [BrowserCookieStoreRecords]
    {
        guard BrowserCookieAccessGate.shouldAttempt(browser) else { return [] }
        return try self.records(matching: query, in: browser, logger: logger)
    }
}
```

A blocked gate returns an empty array (not an error), so callers treat it as "no cookies found" rather than a failure state. This is intentional: the feature gracefully degrades rather than surfacing errors to the UI. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:115-124]()

The `[Browser].cookieImportCandidates(using:)` function applies the gate at the candidate-selection stage, before any fetch is attempted:

```swift
// Sources/CodexBarCore/BrowserCookieImportOrder.swift:17-25
public func cookieImportCandidates(using detection: BrowserDetection) -> [Browser] {
    let candidates = self.filter { browser in
        if KeychainAccessGate.isDisabled, browser.usesKeychainForCookieDecryption { return false }
        return detection.isCookieSourceAvailable(browser)
    }
    return candidates.filter { BrowserCookieAccessGate.shouldAttempt($0) }
}
```

Sources: [Sources/CodexBarCore/BrowserCookieImportOrder.swift:17-25]()

---

## Testing Infrastructure

### Test-Time Keychain Bypass

`KeychainAccessGate.isDisabled` returns `true` automatically when running under the Swift test runner (process name `swiftpm-testing-helper` or `*PackageTests`, or `XCTestConfigurationFilePath` set), unless the environment variable `CODEXBAR_ALLOW_TEST_KEYCHAIN_ACCESS=1` is set. This means tests that use `cookieImportCandidates` automatically drop all Chromium browsers from the candidate list without any explicit mock. Sources: [Sources/CodexBarCore/KeychainAccessGate.swift:34-46]()

This behavior is verified directly in tests:

```swift
// Tests/CodexBarTests/AlibabaCodingPlanCookieImporterTests.swift:49-73
@Test
func `default cookie import candidates skip keychain browsers during tests`() throws {
    BrowserCookieAccessGate.resetForTesting()
    // ... creates Chrome profile on disk ...
    let candidates = AlibabaCodingPlanCookieImporter.cookieImportCandidates(browserDetection: detection)
    #expect(candidates.first == .safari)
    #expect(candidates.contains(.chrome) == false)
}
```

Sources: [Tests/CodexBarTests/AlibabaCodingPlanCookieImporterTests.swift:49-73]()

### `resetForTesting()`

`BrowserCookieAccessGate.resetForTesting()` clears the in-memory denial map, marks state as loaded (bypassing the `UserDefaults` load), and removes the `UserDefaults` key. Tests call this at the start of each test case to prevent cooldown state from leaking between tests:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:76-82
public static func resetForTesting() {
    self.lock.withLock { state in
        state.loaded = true
        state.deniedUntilByBrowser.removeAll()
        UserDefaults.standard.removeObject(forKey: self.defaultsKey)
    }
}
```

### Preflight Override for Testing

`KeychainAccessPreflight` has a `@TaskLocal` override mechanism that lets individual tests inject a fake keychain response without touching the real Security framework:

```swift
// Sources/CodexBarCore/KeychainAccessPreflight.swift:103-112
static func withCheckGenericPasswordOverrideForTesting<T>(
    _ override: ((String, String?) -> Outcome)?,
    operation: () throws -> T) rethrows -> T
{
    try self.$taskCheckGenericPasswordOverrideStore.withValue(
        override.map(CheckGenericPasswordOverrideStore.init(check:))) {
        try operation()
    }
}
```

Sources: [Sources/CodexBarCore/KeychainAccessPreflight.swift:88-124]()

---

## Non-macOS Stub

On Linux (and any other non-macOS platform), the entire `BrowserCookieAccessGate` is replaced with a minimal stub that compiles without importing `SweetCookieKit`, `Security`, or `LocalAuthentication`:

```swift
// Sources/CodexBarCore/BrowserCookieAccessGate.swift:126-135
#else
public enum BrowserCookieAccessGate {
    public static func shouldAttempt(_ browser: Browser, now: Date = Date()) -> Bool { true }
    public static func recordIfNeeded(_ error: Error, now: Date = Date()) {}
    public static func recordDenied(for browser: Browser, now: Date = Date()) {}
    public static func resetForTesting() {}
}
#endif
```

This means the same `CodexBarCore` library builds and runs on Linux for CI purposes, where the full keychain stack is unavailable. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:125-135]()

---

## Summary

`BrowserCookieAccessGate` is a narrow, intentionally blunt instrument: once a keychain interaction is detected or a denial is recorded, it backs off for exactly six hours and returns empty results silently, with no retry and no UI feedback to the end user. The preflight mechanism in `KeychainAccessPreflight` uses two overlapping no-interaction flags (one via `LAContext`, one via a runtime-resolved deprecated constant) because `interactionNotAllowed` alone was historically insufficient to suppress the Allow/Deny dialog on some macOS configurations. The gate is the mandatory entry point for all Chromium cookie reads in CodexBar, enforced through both the `BrowserCookieClient` extension and the `cookieImportCandidates` filter. Sources: [Sources/CodexBarCore/BrowserCookieAccessGate.swift:15]()

---

## 04. Synthetic Provider — Universal Quota Parser That Handles Any API Shape

> The Synthetic provider is designed for any OpenAI-compatible proxy; its quota API can return wildly different JSON shapes. SyntheticUsageParser does not rely on a fixed schema: it tries 10+ key aliases for each field (percentUsed/usagePercent/used_percent/percent_used/percent…), auto-detects whether a percent value is already 0-100 or 0-1, derives missing fields by algebra (used = limit − remaining), and parses duration strings like "5hr", "30min", "2 days" by sorting suffix strings longest-first to prevent prefix collisions. When the known Synthetic API shape is detected (rollingFiveHourLimit / weeklyTokenLimit / search.hourly keys) it uses slot-positioned mapping so a missing middle lane stays nil instead of shifting labels. Timestamp heuristic: numbers > 1e12 are treated as milliseconds, numbers > 1e9 as seconds, strings are tried with and without fractional seconds. Evidence: SyntheticUsageParser.swift, SyntheticUsageParser.windowSuffixMultipliers sorted by suffix length.

- Page Markdown: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/04-synthetic-provider-universal-quota-parser-that-handles-any-api-shape.md
- Generated: 2026-05-18T21:31:14.232Z

### Source Files

- `Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageParser.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageFetcher.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift`
- `Tests/CodexBarTests/VeniceUsageFetcherTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift](Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift)
- [Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift](Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift)
- [Sources/CodexBarCore/Providers/Synthetic/SyntheticSettingsReader.swift](Sources/CodexBarCore/Providers/Synthetic/SyntheticSettingsReader.swift)
- [Sources/CodexBar/Providers/Synthetic/SyntheticProviderImplementation.swift](Sources/CodexBar/Providers/Synthetic/SyntheticProviderImplementation.swift)
- [Tests/CodexBarTests/SyntheticProviderTests.swift](Tests/CodexBarTests/SyntheticProviderTests.swift)
</details>

# Synthetic Provider — Universal Quota Parser That Handles Any API Shape

The Synthetic provider integrates with the [Synthetic.new](https://synthetic.new) OpenAI-compatible proxy. Because OpenAI-compatible proxies expose wildly different JSON shapes for quota/rate-limit APIs, CodexBar ships `SyntheticUsageParser` — a defensive parser that can interpret nearly any quota response without a fixed schema contract.

This page focuses on the non-obvious mechanics of `SyntheticUsageParser` (defined inline in `SyntheticUsageStats.swift`): its alias-first key lookup, algebraic field derivation, fraction-to-percent auto-detection, duration-string parsing, slot-positioned lane mapping, and timestamp heuristics.

---

## Architecture Overview

```
SyntheticAPIFetchStrategy.fetch()
        │
        ▼
SyntheticUsageFetcher.fetchUsage()   ← GET https://api.synthetic.new/v2/quotas
        │  Bearer token from SYNTHETIC_API_KEY or Keychain
        ▼
SyntheticUsageParser.parse(data:now:)
        │
        ├─ prioritizedQuotaSlots()   ← Known Synthetic shape? Use slot-positioned mapping
        │         (rollingFiveHourLimit / weeklyTokenLimit / search.hourly)
        │
        └─ fallbackQuotaObjects()    ← Unknown shape? Walk 13 key candidates for arrays/dicts
                  │
                  └─ parseQuota()   ← Per-entry: alias lookup → algebra → percent normalization
                            │
                            ▼
                  SyntheticUsageSnapshot (quotas + slottedQuotas?)
                            │
                            ▼
                  toUsageSnapshot() → UsageSnapshot (primary / secondary / tertiary)
```

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:154-187]()

---

## Two-Path Parsing: Known Shape vs. Generic Fallback

### Path 1 — Known Synthetic Shape (Slot-Positioned)

If the response contains any of `rollingFiveHourLimit`, `weeklyTokenLimit`, or `search.hourly` (or any of these nested under a top-level `data` key), `prioritizedQuotaSlots()` fires first and returns a three-element array `[rolling-5h, weekly, search-hourly]`. A missing middle lane is `nil`, not absent — this is the critical invariant.

```swift
// SyntheticUsageStats.swift
let slots: [[String: Any]?] = [rolling, weekly, searchHourly]
return slots.contains(where: { $0 != nil }) ? slots : nil
```

`toUsageSnapshot()` maps slot 0 → `primary`, slot 1 → `secondary`, slot 2 → `tertiary`. If `rollingFiveHourLimit` is absent from the response, `primary` is `nil` and the weekly quota does **not** get promoted to the primary UI lane. This prevents label mismatches when the server omits a quota tier.

The test `preserves slot identity when rolling lane is missing` verifies this: a response with only `weeklyTokenLimit` and `search.hourly` yields `usage.primary == nil`, `usage.secondary != nil`, `usage.tertiary != nil`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:192-201](), [Tests/CodexBarTests/SyntheticProviderTests.swift:199-226]()

### Path 2 — Generic Fallback (13-Candidate Key Walk)

When none of the known Synthetic keys appear, `fallbackQuotaObjects()` tries 13 top-level and `data`-nested key names in order:

| Priority | Keys tried |
|----------|-----------|
| 1–6 | `quotas`, `quota`, `limits`, `usage`, `entries`, `subscription` |
| 7–13 | Same six under `root["data"]`, plus `data` itself as a dict |

For each candidate, `extractQuotaObjects()` recursively descends arrays and dicts, collecting any object that contains at least one recognizable numeric field (limit, used, remaining, or percent). Dict keys are iterated in **sorted order** to ensure deterministic extraction.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:204-227]()

---

## Per-Entry Field Resolution (`parseQuota`)

For each quota object, fields are resolved in three layers, shortest-path first:

### 1. Direct Percent Lookup

`percentUsedKeys` holds 7 aliases tried in order:

```
percentUsed, usedPercent, usagePercent, usage_percent, used_percent, percent_used, percent
```

`percentRemainingKeys` holds 4 aliases. If used-percent is not found but remaining-percent is, `usedPercent = 100 − remaining`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:570-585]()

### 2. Algebraic Derivation

If no percent field exists, the parser reads limit / used / remaining via their respective alias sets and derives the missing value algebraically:

```swift
if limit == nil, let used, let remaining { limit = used + remaining }
if used == nil, let limit, let remaining { used = limit - remaining }
if remaining == nil, let limit, let used { remaining = max(0, limit - used) }
if let limit, let used, limit > 0 { usedPercent = (used / limit) * 100 }
```

`limitKeys` covers 12 aliases (`limit`, `messageLimit`, `max_requests`, `quota`, `total`, `capacity`, `allowance`, …); `usedKeys` covers 11 (`used`, `usage`, `consumed`, `spent`, …); `remainingKeys` covers 4 (`remaining`, `left`, `available`, `balance`).

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:252-269]()

### 3. Percent Auto-Normalization

A percent value ≤ 1 is treated as a 0–1 fraction and multiplied by 100. A value > 1 is kept as-is. This handles APIs that report `0.75` (75%) and APIs that report `75` with the same code path:

```swift
private static func normalizedPercent(_ value: Double?) -> Double? {
    guard let value else { return nil }
    if value <= 1 { return value * 100 }
    return value
}
```

The live Synthetic response uses `tickPercent: 0.05` (meaning 5%), which becomes `nextRegenPercent == 5.0` after normalization. This is verified in the `parses rolling lane tickPercent into primary nextRegenPercent` test.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:454-458](), [Tests/CodexBarTests/SyntheticProviderTests.swift:149-165]()

---

## Duration String Parsing (`windowMinutes(fromText:)`)

Window durations may arrive as human strings: `"5hr"`, `"30min"`, `"2 days"`, `"1 hour"`. The parser normalizes the string (trim, lowercase, remove spaces) then iterates `windowSuffixMultipliers`.

### The Longest-First Sort

The suffix table is built at init time sorted by `suffix.count` descending:

```swift
private static let windowSuffixMultipliers: [(suffix: String, multiplier: Double)] = {
    let raw: [(String, Double)] = [
        ("minutes", 1), ("minute", 1), ("mins", 1), ("min", 1), ("m", 1),
        ("hours", 60), ("hour", 60), ("hrs", 60), ("hr", 60), ("h", 60),
        ("days", 24 * 60), ("day", 24 * 60), ("d", 24 * 60),
    ]
    return raw
        .sorted { $0.0.count > $1.0.count }
        .map { (suffix: $0.0, multiplier: $0.1) }
}()
```

Without longest-first sorting, `"5hours"` would incorrectly match the single-char suffix `"s"` before reaching `"hours"`. By checking longer suffixes first, multi-letter units always win over their shorter aliases. This is not merely a performance choice — it is a correctness constraint that prevents silent misparses.

The test suite verifies all suffix variants: `"5min"→5`, `"5m"→5`, `"5hr"→300`, `"5h"→300`, `"5hours"→300`, `"2days"→2880`, `"2d"→2880`, `"1 hour"→60`, `"junk"→nil`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:378-387](), [Tests/CodexBarTests/SyntheticProviderTests.swift:185-196]()

---

## Timestamp Heuristics (`dateValue`)

The `firstDate(in:keys:)` helper resolves reset/expiry timestamps from 18 key aliases (`resetAt`, `reset_at`, `resetsAt`, `renewAt`, `nextTickAt`, `periodEnd`, `expiresAt`, …). Each candidate value is passed through `dateValue()`:

```swift
private static func dateValue(_ raw: Any) -> Date? {
    if let number = self.doubleValue(raw) {
        if number > 1_000_000_000_000 { return Date(timeIntervalSince1970: number / 1000) }
        if number > 1_000_000_000    { return Date(timeIntervalSince1970: number) }
    }
    if let string = raw as? String {
        if let number = Double(string.trimmingCharacters(in: .whitespacesAndNewlines)) {
            return self.dateValue(number)  // numeric string → re-enter heuristic
        }
        if let date = SyntheticTimestampParser.parse(string) { return date }
    }
    return nil
}
```

The thresholds distinguish milliseconds (> 1 × 10¹²) from seconds (> 1 × 10⁹) without any explicit type tag. The `SyntheticTimestampParser` tries ISO 8601 with fractional seconds first, then plain ISO 8601 without — covering both `"2026-04-17T04:30:01.494Z"` and `"2026-04-17T03:44:11Z"`. The formatter pair is guarded by `NSLock` since ISO 8601 formatters are not thread-safe.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:529-547](), [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:129-152]()

---

## Reset Description: Live vs. Static

There is a subtle intentional asymmetry in `parseQuota`: when `resetsAt` is populated, `resetDescription` is set to `nil`:

```swift
// Leave resetDescription nil when resetsAt is set so the UI rebuilds the countdown each render
// against the current clock instead of freezing a stale "in Xm" string at parse time.
let resetDescription = resetsAt == nil ? self.windowDescription(minutes: windowMinutes) : nil
```

When a reset timestamp is known, the UI layer computes a live countdown on every render. Only when no timestamp is available does the parser generate a static fallback string like `"5 hours window"`. This prevents stale countdown strings if the parsed snapshot is cached between refreshes.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:277-279]()

---

## Cost Snapshot Extraction

When a quota object contains `maxCredits` or `max_credits`, `providerCost()` produces a `ProviderCostSnapshot` with USD amounts. It strips currency symbols and commas before parsing (`"$36.00"` → `36.0`). The `used` amount is resolved in priority order: explicit `usedCredits` field → `limit − remainingCredits` → `(usedPercent / 100) × limit`. `nextRegenCredits` (alias `next_regen_credits`) is surfaced as `nextRegenAmount` for regeneration indicators.

In the live Synthetic API shape, `weeklyTokenLimit.remainingCredits = "$35.30"` with `maxCredits = "$36.00"` yields `used = 0.70`. The test `parses live root level rolling and weekly quotas` verifies `providerCost?.used ≈ 0.7`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:403-429](), [Tests/CodexBarTests/SyntheticProviderTests.swift:93-146]()

---

## Provider Configuration

The provider is registered as non-primary and disabled by default. Its three display lanes are labelled **Five-hour quota**, **Weekly tokens**, and **Search hourly** — matching the three known Synthetic API slots. Token cost reporting is disabled (`supportsTokenCost: false`). The API key is resolved from the `SYNTHETIC_API_KEY` environment variable or from `~/.codexbar/config.json`; the settings reader strips surrounding single or double quotes to tolerate copy-paste artefacts.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift:8-39](), [Sources/CodexBarCore/Providers/Synthetic/SyntheticSettingsReader.swift:13-28]()

---

## Summary

`SyntheticUsageParser` is a schema-free defensive parser: it tries dozens of key aliases, applies algebraic field derivation when direct values are absent, auto-detects 0–1 vs. 0–100 percent encoding, and handles arbitrary timestamp and duration formats. The one place it departs from pure genericity is the `prioritizedQuotaSlots` path — when the known Synthetic API shape is detected, slot-positional mapping guarantees that a missing quota tier stays `nil` in its UI lane rather than silently promoting the next lane, preventing label mismatches that would otherwise be invisible from the outside. The full parsing logic, including all alias tables and edge cases, lives in `SyntheticUsageStats.swift`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:154-387]()

---

## 05. Claude Peak-Hour Awareness — ET 8am–2pm Weekdays

> ClaudePeakHours hard-codes Anthropic's known rate-limit peak window: weekdays 08:00–14:00 America/New_York. The status() method returns a labeled Status struct with a countdown ("Peak · ends in 2h 15m" or "Off-peak · peak in 18h 30m") computed against the Eastern timezone calendar. Weekends are always off-peak. The next-peak search correctly skips Saturday (skip=2 days) and Sunday (skip=1 day) so Monday is always the next target. ClaudeSourcePlanner uses this signal alongside credential availability and ProviderRuntime to decide which data source to attempt, making the fetch strategy time-aware without any external configuration. Evidence: ClaudePeakHours.swift peakStartHour=8, peakEndHour=14, peakTimeZone="America/New_York"; ClaudeSourcePlannerTests.swift.

- Page Markdown: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/05-claude-peak-hour-awareness-et-8am-2pm-weekdays.md
- Generated: 2026-05-18T21:29:51.034Z

### Source Files

- `Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift`
- `Sources/CodexBarCore/Providers/Claude/ClaudeSourcePlanner.swift`
- `Sources/CodexBarCore/Providers/Claude/ClaudeCredentialRouting.swift`
- `Tests/CodexBarTests/ClaudeSourcePlannerTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift](Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift)
- [Sources/CodexBarCore/Providers/Claude/ClaudeSourcePlanner.swift](Sources/CodexBarCore/Providers/Claude/ClaudeSourcePlanner.swift)
- [Sources/CodexBarCore/Providers/Claude/ClaudeCredentialRouting.swift](Sources/CodexBarCore/Providers/Claude/ClaudeCredentialRouting.swift)
- [Tests/CodexBarTests/ClaudePeakHoursTests.swift](Tests/CodexBarTests/ClaudePeakHoursTests.swift)
- [Tests/CodexBarTests/ClaudeSourcePlannerTests.swift](Tests/CodexBarTests/ClaudeSourcePlannerTests.swift)
- [Sources/CodexBar/MenuCardView.swift](Sources/CodexBar/MenuCardView.swift)
- [Sources/CodexBar/Providers/Claude/ClaudeProviderImplementation.swift](Sources/CodexBar/Providers/Claude/ClaudeProviderImplementation.swift)
- [Sources/CodexBar/SettingsStore.swift](Sources/CodexBar/SettingsStore.swift)
- [Sources/CodexBar/SettingsStore+Defaults.swift](Sources/CodexBar/SettingsStore+Defaults.swift)
</details>

# Claude Peak-Hour Awareness — ET 8am–2pm Weekdays

`ClaudePeakHours` is a small but precise time-zone–aware module that hard-codes Anthropic's known rate-limit peak window — weekdays 08:00–14:00 America/New_York — and exposes it as a human-readable `Status` struct. Every call to `status(at:)` returns either `"Peak · ends in Xh Ym"` or `"Off-peak · peak in Xh Ym"`, computed live from the Eastern calendar with no external configuration or network call.

This signal flows directly into the menu-bar card: when the Claude provider is active and the user has enabled the indicator, the label is the sole entry in the `usageNotes` array that appears below the usage bar. It is also the only provider-specific badge in the app that encodes time rather than account state, making it a first-class UI affordance for managing API quota during high-contention hours.

---

## Hard-Coded Window and Timezone

`ClaudePeakHours` intentionally hard-codes all three constants as private statics:

```swift
// Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:4-6
private static let peakTimeZone = TimeZone(identifier: "America/New_York")!
private static let peakStartHour = 8
private static let peakEndHour   = 14
```

There is no injection point, no plist key, and no remote flag. This is a deliberate trade-off: the window reflects an empirically observed Anthropic rate-limit pattern, not a dynamically advertised one, so runtime configurability would only add complexity without reducing staleness risk. If Anthropic changes the window, a code update is required.

The forced unwrap (`!`) on the timezone is safe because `"America/New_York"` is a fixed IANA identifier that ships with every Apple OS. It would only fail on a pathologically stripped environment, which the app does not support.

Sources: [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:4-6]()

---

## `status(at:)` — Minute-Floor Truncation

```swift
// ClaudePeakHours.swift:15
let date = calendar.dateInterval(of: .minute, for: date)?.start ?? date
```

Before any comparison the input `Date` is floored to the start of its minute using `dateInterval(of: .minute, for:)`. This means a timestamp of `07:59:59` reports the same label as `07:59:00` — the sub-minute portion is silently discarded. The test suite verifies this explicitly:

```swift
// Tests/CodexBarTests/ClaudePeakHoursTests.swift:141-144
func `weekday one minute before peak with seconds`() {
    let status = ClaudePeakHours.status(at: self.date(day: 25, hour: 7, minute: 59, second: 30))
    #expect(status.label == "Off-peak · peak in 1m")
}
```

This truncation prevents the countdown from showing `"Off-peak · peak in 0m"` for a date that is, say, 45 seconds away from 08:00 ET — the minimum granularity the label communicates is one minute.

Sources: [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:15](), [Tests/CodexBarTests/ClaudePeakHoursTests.swift:139-158]()

---

## Peak Detection Logic

After truncation, the method extracts `hour`, `minute`, and `weekday` from the Eastern calendar. Weekdays are identified by Swift's `Calendar` convention where Sunday = 1 and Saturday = 7, so the weekday range `2…6` maps to Monday–Friday:

```swift
// ClaudePeakHours.swift:25-29
let isWeekday = weekday >= 2 && weekday <= 6
let nowMinutes = hour * 60 + minute
let peakStartMinutes = self.peakStartHour * 60   // 480
let peakEndMinutes   = self.peakEndHour   * 60   // 840
let isInPeakWindow   = nowMinutes >= peakStartMinutes && nowMinutes < peakEndMinutes
```

The comparison is `>= start` and `< end`, so the peak boundary at 14:00 ET is **exclusive** — `13:59` is still peak; `14:00` is off-peak. The test confirms:

```swift
// ClaudePeakHoursTests.swift:57-60
func `weekday peak end boundary`() {
    let status = ClaudePeakHours.status(at: self.date(day: 25, hour: 13, minute: 59))
    #expect(status.label == "Peak · ends in 1m")
}
// ClaudePeakHoursTests.swift:63-67
func `weekday after peak`() {
    let status = ClaudePeakHours.status(at: self.date(day: 25, hour: 14))
    #expect(status.label == "Off-peak · peak in 18h")
}
```

Sources: [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:25-36](), [Tests/CodexBarTests/ClaudePeakHoursTests.swift:55-67]()

---

## Next-Peak Calculation and Weekend Skip Logic

`nextPeakStart(after:calendar:)` is the most subtle function in the module. It must correctly skip Saturday and Sunday to land on Monday:

```swift
// ClaudePeakHours.swift:46-63
private static func nextPeakStart(after date: Date, calendar: Calendar) -> Date {
    guard let todayPeak = calendar.date(
        bySettingHour: self.peakStartHour, minute: 0, second: 0, of: date)
    else { return date }

    // If today's 08:00 ET is still in the future, use it; else advance one day.
    let anchor = todayPeak > date
        ? todayPeak
        : calendar.date(byAdding: .day, value: 1, to: todayPeak) ?? date

    let weekday = calendar.component(.weekday, from: anchor)

    let skip = switch weekday {
    case 1: 1   // Sunday  → add 1 day → Monday
    case 7: 2   // Saturday → add 2 days → Monday
    default: 0  // Mon–Fri: already a weekday
    }

    if skip == 0 { return anchor }
    return calendar.date(byAdding: .day, value: skip, to: anchor) ?? anchor
}
```

### Why `skip=2` for Saturday and `skip=1` for Sunday

The `anchor` is always set to 08:00 ET on some candidate day. After the "advance one day" step, an input on Friday after 14:00 produces a Saturday anchor. Saturday is `weekday == 7`, so `skip = 2` advances to Monday 08:00 ET. An input on Saturday morning produces a Sunday anchor (`weekday == 1`), so `skip = 1` advances to Monday. An input on Sunday produces a Monday anchor directly (`weekday == 2`, `skip = 0`).

The test suite validates the most time-distant case:

```swift
// ClaudePeakHoursTests.swift:92-95
func `friday after peak`() {
    let status = ClaudePeakHours.status(at: self.date(day: 27, hour: 15))
    #expect(status.label == "Off-peak · peak in 65h")  // Friday 15:00 → Monday 08:00
}
```

And a DST edge case (spring-forward weekend, 2026-03-07 is a Saturday):

```swift
// ClaudePeakHoursTests.swift:105-109
func `spring forward weekend`() {
    let status = ClaudePeakHours.status(at: self.date(day: 7, hour: 10))
    #expect(status.label == "Off-peak · peak in 45h")
}
```

The use of `Calendar` with the Eastern timezone means DST transitions are handled automatically by Foundation — no manual offset arithmetic is needed.

Sources: [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:46-63](), [Tests/CodexBarTests/ClaudePeakHoursTests.swift:77-109]()

---

## Duration Formatting

`formatDuration(minutes:)` produces compact labels without redundant zero components:

| Minutes | Output |
|---------|--------|
| 360 | `"6h"` |
| 150 | `"2h 30m"` |
| 15 | `"15m"` |
| 1 | `"1m"` |

```swift
// ClaudePeakHours.swift:66-76
private static func formatDuration(minutes: Int) -> String {
    let h = minutes / 60
    let m = minutes % 60
    if h == 0 { return "\(m)m" }
    if m == 0 { return "\(h)h" }
    return "\(h)h \(m)m"
}
```

The hours-only path (`m == 0`) avoids labels like `"6h 0m"`, and the minutes-only path keeps short countdowns readable. There is no "0m" guard — the upstream `max(Int(seconds / 60), 0)` clamp ensures negative results from floating-point imprecision are floored at zero, so `"0m"` is theoretically possible but only if `nextPeakStart` returns a date equal to the floored input.

Sources: [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:66-76]()

---

## UI Integration and Toggle

The label produced by `ClaudePeakHours.status(at:)` surfaces in the menu-bar card's `usageNotes` array. The integration point is:

```swift
// Sources/CodexBar/MenuCardView.swift:839-842
if input.provider == .claude, input.claudePeakHoursEnabled {
    let peakStatus = ClaudePeakHours.status(at: input.now)
    return [peakStatus.label]
}
```

The feature is **opt-out** (default `true`), stored in `UserDefaults` under key `"claudePeakHoursEnabled"`:

```swift
// Sources/CodexBar/SettingsStore.swift:353
let claudePeakHoursEnabled = userDefaults.object(forKey: "claudePeakHoursEnabled") as? Bool ?? true
```

The preferences pane exposes it as a toggle labelled **"Show peak hours indicator"** with subtitle `"Show whether Claude is in peak usage hours."`:

```swift
// Sources/CodexBar/Providers/Claude/ClaudeProviderImplementation.swift:99-103
ProviderSettingsToggleDescriptor(
    id: "claude-peak-hours",
    title: "Show peak hours indicator",
    subtitle: "Show whether Claude is in peak usage hours.",
    binding: peakHoursBinding, …)
```

The `usageNotes` function only returns the peak-hours label for `.claude` — no other provider (Kilo, Kiro, Mimo) participates in this branch.

Sources: [Sources/CodexBar/MenuCardView.swift:839-842](), [Sources/CodexBar/Providers/Claude/ClaudeProviderImplementation.swift:83-110](), [Sources/CodexBar/SettingsStore.swift:353]()

---

## State Flow Diagram

```mermaid
flowchart TD
    A[Input: Date + claudePeakHoursEnabled] --> B{provider == .claude\nAND enabled?}
    B -- No --> C[No peak-hours note shown]
    B -- Yes --> D[ClaudePeakHours.status(at: now)]
    D --> E{Weekday\n08:00–13:59 ET?}
    E -- Yes --> F["Peak · ends in Xh Ym"]
    E -- No --> G[nextPeakStart(after:)]
    G --> H{anchor weekday}
    H -- Saturday --> I[skip +2 days → Monday]
    H -- Sunday --> J[skip +1 day → Monday]
    H -- Mon–Fri --> K[anchor as-is]
    I & J & K --> L["Off-peak · peak in Xh Ym"]
    F & L --> M[usageNotes array in MenuCardView]
```

---

## Key Behavioral Contracts (Test-Verified)

| Scenario | Expected Label | Source |
|---|---|---|
| Monday 00:00 ET | `"Off-peak · peak in 8h"` | `ClaudePeakHoursTests:112-116` |
| Monday 07:00 ET | `"Off-peak · peak in 1h"` | `ClaudePeakHoursTests:28-31` |
| Monday 08:00 ET | `"Peak · ends in 6h"` | `ClaudePeakHoursTests:42-45` |
| Wednesday 11:30 ET | `"Peak · ends in 2h 30m"` | `ClaudePeakHoursTests:49-53` |
| Wednesday 13:59 ET | `"Peak · ends in 1m"` | `ClaudePeakHoursTests:56-60` |
| Wednesday 14:00 ET | `"Off-peak · peak in 18h"` | `ClaudePeakHoursTests:63-67` |
| Friday 15:00 ET | `"Off-peak · peak in 65h"` | `ClaudePeakHoursTests:92-95` |
| Saturday 00:00 ET | `"Off-peak · peak in 56h"` | `ClaudePeakHoursTests:127-130` |
| Saturday 10:00 ET | `"Off-peak · peak in 46h"` | `ClaudePeakHoursTests:77-81` |
| Sunday 21:00 ET | `"Off-peak · peak in 11h"` | `ClaudePeakHoursTests:84-88` |
| DST spring-forward Sat 10:00 ET | `"Off-peak · peak in 45h"` | `ClaudePeakHoursTests:105-109` |

---

## Relationship to `ClaudeSourcePlanner`

`ClaudeSourcePlanner` resolves an ordered list of data-source steps (OAuth → CLI → Web for the app runtime; Web → CLI for the CLI runtime) based on credential availability and `ProviderRuntime`. While `ClaudeSourcePlanner` does not directly call `ClaudePeakHours`, both feed into the same menu-card rendering pipeline and share the same `ClaudeSourcePlanningInput` context. The planner determines *which* source to attempt; the peak-hours badge signals *whether* the chosen source is likely to be rate-limited. The combination makes the fetch strategy time-aware without any external configuration change required.

Sources: [Sources/CodexBarCore/Providers/Claude/ClaudeSourcePlanner.swift:166-223](), [Tests/CodexBarTests/ClaudeSourcePlannerTests.swift:6-98]()

---

The entire peak-hour feature is self-contained within `ClaudePeakHours.swift` (84 lines) and requires no network, no background timer, and no shared mutable state — `status(at:)` is a pure function of the supplied `Date`, making it trivially testable and safe to call on any thread (`Sendable` conformance is declared on both the enum and `Status`). Sources: [Sources/CodexBarCore/Providers/Claude/ClaudePeakHours.swift:3-83]()

---

## 06. DisplayLink Dual-Path & Watchdog Orphan Detection

> Two unrelated but equally subtle runtime quirks shape how CodexBar animates its icon and manages child processes. DisplayLinkDriver uses NSScreen.displayLink (macOS 15+) when available and falls back to CVDisplayLink (macOS 14), bridging the two via a scheduleTick() dispatch to the main actor since CVDisplayLink fires on a background thread. CADisplayLink ticks are rate-limited by a manual timestamp comparison so the 12 fps target is honored regardless of the display's native refresh rate. Separately, CodexBarClaudeWatchdog is a standalone POSIX executable that spawns claude as a child process group leader and polls waitpid every 200ms. Its critical invariant: if getppid() returns 1, the macOS app has been killed without sending SIGTERM, so the watchdog kills the child process tree (SIGTERM → 500ms grace → SIGKILL) and exits. The watchdog also manually decodes waitpid's raw status integer because Swift cannot import function-like C macros (WIFEXITED/WEXITSTATUS). Evidence: DisplayLink.swift startCVDisplayLink(), CodexBarClaudeWatchdog/main.swift getppid()==1 branch, exitCode(fromWaitStatus:) bit-manipulation.

- Page Markdown: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/pages/06-displaylink-dual-path-watchdog-orphan-detection.md
- Generated: 2026-05-18T21:30:45.829Z

### Source Files

- `Sources/CodexBar/DisplayLink.swift`
- `Sources/CodexBarClaudeWatchdog/main.swift`
- `Sources/CodexBarCore/Host/PTY/TTYCommandRunner.swift`
- `Sources/CodexBarCore/Host/Process/SubprocessRunner.swift`
- `Tests/CodexBarTests/TTYCommandRunnerTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [Sources/CodexBar/DisplayLink.swift](Sources/CodexBar/DisplayLink.swift)
- [Sources/CodexBarClaudeWatchdog/main.swift](Sources/CodexBarClaudeWatchdog/main.swift)
- [Sources/CodexBarCore/Host/PTY/TTYCommandRunner.swift](Sources/CodexBarCore/Host/PTY/TTYCommandRunner.swift)
- [Sources/CodexBarCore/Host/Process/SubprocessRunner.swift](Sources/CodexBarCore/Host/Process/SubprocessRunner.swift)
- [Tests/CodexBarTests/TTYCommandRunnerTests.swift](Tests/CodexBarTests/TTYCommandRunnerTests.swift)
</details>

# DisplayLink Dual-Path & Watchdog Orphan Detection

Two separately-motivated but equally subtle runtime mechanisms keep CodexBar correct under conditions that most apps never encounter. `DisplayLinkDriver` must animate a menu-bar icon at a fixed 12 fps across macOS versions that offer incompatible display-link APIs, one of which fires on a background thread and must be safely bridged to the `@MainActor`. `CodexBarClaudeWatchdog` is a POSIX helper process that must clean up a `claude` child even when the macOS parent app is force-killed without sending `SIGTERM` — a scenario that requires POSIX orphan detection and hand-written wait-status bit manipulation that Swift's type system cannot supply.

Together these components demonstrate the kind of defensive engineering that becomes necessary when you wrap long-lived, interactive Unix processes inside a Cocoa application: neither the display refresh contract nor the process lifecycle contract can be assumed to hold under all OS versions and shutdown paths.

---

## DisplayLink Dual-Path

### The API split between macOS 14 and macOS 15

Apple introduced `NSScreen.displayLink(target:selector:)` in macOS 15 and deprecated the C-level `CVDisplayLink` API family alongside it. CodexBar must run on macOS 14, so `DisplayLinkDriver` contains an explicit version fork.

```swift
// Sources/CodexBar/DisplayLink.swift  lines 29-41
if #available(macOS 15, *), let screen = NSScreen.main {
    let displayLink = screen.displayLink(target: self, selector: #selector(self.step))
    let rate = Float(clampedFps)
    displayLink.preferredFrameRateRange = CAFrameRateRange(
        minimum: rate, maximum: rate, preferred: rate)
    displayLink.add(to: .main, forMode: .common)
    self.displayLink = displayLink
} else {
    self.startCVDisplayLink()
}
```

On macOS 15 the resulting `CADisplayLink` is added to the main run loop; its callback `step(_:)` fires on the main thread, and the class's `@MainActor` isolation means no additional dispatch is needed.

Sources: [Sources/CodexBar/DisplayLink.swift:29-41]()

### CVDisplayLink and the background-thread bridge

`CVDisplayLink` callbacks arrive on an arbitrary background thread, not the main thread. Because `DisplayLinkDriver` is `@MainActor`-isolated, calling `handleTick()` directly from the callback would violate Swift concurrency rules and could produce data races on `tick` and `lastTickTimestamp`.

The fix is `scheduleTick()`, which is marked `nonisolated` so it can be called from outside the actor, and dispatches to `@MainActor` via a `Task`:

```swift
// Sources/CodexBar/DisplayLink.swift  lines 74-89
let callback: CVDisplayLinkOutputCallback = { _, _, _, _, _, userInfo in
    guard let userInfo else { return kCVReturnSuccess }
    let driver = Unmanaged<DisplayLinkDriver>.fromOpaque(userInfo).takeUnretainedValue()
    driver.scheduleTick()
    return kCVReturnSuccess
}
...
private nonisolated func scheduleTick() {
    Task { @MainActor [weak self] in
        self?.handleTick()
    }
}
```

The `Unmanaged.passUnretained` / `takeUnretainedValue` bridge avoids a retain cycle — the C callback holds a raw opaque pointer, not a strong reference.

Sources: [Sources/CodexBar/DisplayLink.swift:74-89]()

### Rate limiting via manual timestamp comparison

`CADisplayLink.preferredFrameRateRange` only hints at the desired rate on macOS 15; the underlying display may run at 60 Hz, 120 Hz, or ProMotion 240 Hz. On macOS 14 (`CVDisplayLink`) there is no built-in rate clamping at all.

`handleTick()` solves this with a single manual guard:

```swift
// Sources/CodexBar/DisplayLink.swift  lines 57-66
private func handleTick() {
    let now = CACurrentMediaTime()
    if self.lastTickTimestamp > 0, now - self.lastTickTimestamp < self.targetInterval {
        return
    }
    self.lastTickTimestamp = now
    self.tick &+= 1
    self.onTick?()
}
```

`targetInterval` is set in `start(fps:)` as `1.0 / clampedFps`. At the default 12 fps that is ≈83 ms. Any display callback that fires before 83 ms have elapsed is silently dropped. This ensures the `tick` counter that drives SwiftUI animations increments at most 12 times per second regardless of the panel's native refresh rate.

`tick` is incremented with `&+=` (wrapping addition) so the `Int` can never trap on overflow, which would otherwise crash the app after ~292 years of continuous use or a counter reset.

Sources: [Sources/CodexBar/DisplayLink.swift:24-27, 57-66]()

### Component summary

| Concern | macOS 15 path | macOS 14 path |
|---|---|---|
| API | `NSScreen.displayLink` → `CADisplayLink` | `CVDisplayLinkCreateWithActiveCGDisplays` |
| Thread | Main (run loop) | Background (C callback) |
| Bridge needed | No | Yes — `scheduleTick()` + `Task @MainActor` |
| Rate clamping | `preferredFrameRateRange` + `handleTick` guard | `handleTick` guard only |
| Storage field | `displayLink: CADisplayLink?` | `cvDisplayLink: CVDisplayLink?` |

---

## Watchdog Orphan Detection

### Why a separate process is needed

When macOS force-kills an app (via Activity Monitor "Force Quit", `kill -9`, or OOM), it sends `SIGKILL`, which cannot be caught. Any child process spawned with `Process` or `posix_spawn` from within the app becomes an orphan whose parent PID is immediately reparented to `launchd` (PID 1). A `claude` session left running in this state holds a PTY, consumes tokens, and may interact with a working directory the user no longer wants touched.

`CodexBarClaudeWatchdog` is a standalone Mach-O helper bundled inside `CodexBar.app/Contents/Helpers/` that addresses this by running as its own PID and polling `getppid()`.

### Spawn path and process group isolation

`TTYCommandRunner.run(binary:send:options:)` detects when the target binary is `claude` and transparently substitutes the watchdog:

```swift
// Sources/CodexBarCore/Host/PTY/TTYCommandRunner.swift  lines 426-434
if resolvedURL.lastPathComponent == "claude",
   let watchdog = Self.locateBundledHelper("CodexBarClaudeWatchdog")
{
    proc.executableURL = URL(fileURLWithPath: watchdog)
    proc.arguments = ["--", resolved] + options.extraArgs
}
```

The watchdog then spawns `claude` via `posix_spawnp` and immediately moves it into its own process group:

```swift
// Sources/CodexBarClaudeWatchdog/main.swift  lines 71-85
let rc: Int32 = childBinary.withCString { childPath in
    posix_spawnp(&pid, childPath, nil, nil, cBuffer.baseAddress, environ)
}
if rc == 0, pid > 0 {
    globalChildPID = pid
}
...
_ = setpgid(globalChildPID, globalChildPID)
```

Setting `pgid = pid` creates a new process group with `claude` as the leader. This lets the watchdog later send `kill(-pgid, ...)` to target the entire subtree (e.g. shell children spawned by `claude` itself), not just the top-level process.

Sources: [Sources/CodexBarClaudeWatchdog/main.swift:62-85]()

### The orphan detection loop

The main poll loop runs every 200 ms (`usleep(200_000)`):

```swift
// Sources/CodexBarClaudeWatchdog/main.swift  lines 101-122
while true {
    let rc = waitpid(globalChildPID, &status, WNOHANG)
    if rc == globalChildPID {
        Darwin.exit(exitCode(fromWaitStatus: status))   // child exited naturally
    }

    if globalShouldTerminate != 0 {                     // SIGTERM/SIGINT/SIGHUP received
        terminateChild()
        _ = waitpid(globalChildPID, &status, 0)
        Darwin.exit(128 + sig)
    }

    if getppid() == 1 {                                 // parent was force-killed
        terminateChild()
        _ = waitpid(globalChildPID, &status, 0)
        Darwin.exit(exitCode(fromWaitStatus: status))
    }

    usleep(200_000)
}
```

Three distinct exit conditions are checked in order:

1. **Natural exit**: `waitpid` with `WNOHANG` returns the child's PID — forward the exit code.
2. **Signal forwarding**: `SIGTERM`, `SIGINT`, or `SIGHUP` set `globalShouldTerminate` asynchronously. The main loop detects this and propagates the signal to the child tree.
3. **Orphan detection**: `getppid() == 1` means the watchdog's parent (the Cocoa app) no longer exists; it was killed hard. The watchdog kills the child tree and exits.

Sources: [Sources/CodexBarClaudeWatchdog/main.swift:101-122]()

### `killProcessTree`: SIGTERM → grace period → SIGKILL

```swift
// Sources/CodexBarClaudeWatchdog/main.swift  lines 17-38
private func killProcessTree(childPID: pid_t, graceSeconds: TimeInterval = 0.5) {
    let pgid = getpgid(childPID)
    if pgid > 0 {
        kill(-pgid, SIGTERM)
    } else {
        kill(childPID, SIGTERM)
    }
    let deadline = Date().addingTimeInterval(graceSeconds)
    var status: Int32 = 0
    while Date() < deadline {
        let rc = waitpid(childPID, &status, WNOHANG)
        if rc == childPID { return }
        usleep(50000)
    }
    if pgid > 0 {
        kill(-pgid, SIGKILL)
    } else {
        kill(childPID, SIGKILL)
    }
}
```

The grace window is 500 ms by default. During this window the helper polls `waitpid` every 50 ms — if `claude` exits voluntarily after `SIGTERM`, the `SIGKILL` is never sent. If the process does not exit within 500 ms, `SIGKILL` fires unconditionally.

Sources: [Sources/CodexBarClaudeWatchdog/main.swift:17-38]()

### Hand-written wait-status decoding

Swift cannot import function-like C macros (`WIFEXITED`, `WEXITSTATUS`, `WIFSIGNALED`), because the preprocessor expands them into bit-manipulation expressions that are invisible to the Swift importer. `exitCode(fromWaitStatus:)` reimplements the POSIX encoding directly:

```swift
// Sources/CodexBarClaudeWatchdog/main.swift  lines 40-52
private func exitCode(fromWaitStatus status: Int32) -> Int32 {
    // Swift can't import wait(2) macros (function-like macros). Use the classic encoding:
    // - low 7 bits: signal number (0 means exited)
    // - high byte: exit status (when exited)
    let low = status & 0x7F
    if low == 0 {
        return (status >> 8) & 0xFF   // normal exit — extract high byte
    }
    if low != 0x7F {
        return 128 + low              // killed by signal — 128 + signal number
    }
    return 1                          // stopped/continued — treat as error
}
```

This matches the POSIX `wait(2)` encoding: bits 0–6 are the terminating signal number (0 if the process called `exit()`), bits 8–15 hold the exit status when the low byte is zero. The `128 + signal` convention mirrors shell semantics so callers can distinguish normal exits from signal deaths.

Sources: [Sources/CodexBarClaudeWatchdog/main.swift:40-52]()

### Watchdog lifecycle diagram

```mermaid
sequenceDiagram
    participant App as CodexBar.app
    participant WD as CodexBarClaudeWatchdog
    participant Claude as claude (child)

    App->>WD: posix_spawn via TTYCommandRunner
    WD->>Claude: posix_spawnp
    WD->>WD: setpgid(claude_pid, claude_pid)
    loop Every 200 ms
        WD->>WD: waitpid(WNOHANG)
        WD->>WD: check globalShouldTerminate
        WD->>WD: getppid() == 1?
    end
    alt App killed with SIGKILL
        WD->>WD: getppid() returns 1
        WD->>Claude: kill(-pgid, SIGTERM)
        WD->>WD: 500 ms grace window
        WD->>Claude: kill(-pgid, SIGKILL) if still running
        WD->>WD: Darwin.exit(...)
    else Claude exits naturally
        WD->>WD: waitpid returns claude_pid
        WD->>WD: exitCode(fromWaitStatus:)
        WD->>App: Darwin.exit(code)
    end
```

---

## Interaction with TTYCommandRunner's Shutdown Registry

`TTYCommandRunner` also maintains its own `TTYCommandRunnerActiveProcessRegistry` for the normal (non-force-kill) shutdown path. When the app quits gracefully, `terminateActiveProcessesForAppShutdown()` drains the registry and sends `SIGTERM` + `SIGKILL` to every tracked PID and its process group. The watchdog path complements this: graceful shutdown is handled by the registry, while hard kills are handled by the watchdog's `getppid() == 1` branch. They cover disjoint failure modes and do not conflict.

The registry protects against a subtle race: once `drainForShutdown()` sets `isShuttingDown = true`, any new `register(pid:binary:)` call returns `false`, and `TTYCommandRunner.run` throws `launchFailed("App shutdown in progress")` immediately after launching, so the newly-spawned watchdog is cleaned up by the `defer { cleanup() }` block before becoming permanently untracked.

Sources: [Sources/CodexBarCore/Host/PTY/TTYCommandRunner.swift:157-179, 509-519](), [Tests/CodexBarTests/TTYCommandRunnerTests.swift:62-70]()

---

These two mechanisms — the display-link API fork with main-actor bridging, and the POSIX watchdog with orphan detection and hand-rolled wait-status decoding — are representative of CodexBar's approach to correctness at the OS boundary: each quirk targets a specific failure mode (API unavailability, hard process kill) that higher-level Swift or Cocoa abstractions cannot address on their own. The watchdog's `getppid() == 1` check is the critical invariant; without it, any force-quit of CodexBar leaves a running `claude` session that the operating system will never clean up automatically. Sources: [Sources/CodexBarClaudeWatchdog/main.swift:115-119]()

---