How to Handle Errors in CodexBar Scanning: A Type-Driven Approach

CodexBar handles scanning errors through provider-specific enums conforming to LocalizedError, centralized orchestration via ProviderFetchPlan, and observable UI propagation to ensure robust data aggregation across dozens of AI providers.

Handling network failures, authentication issues, and parsing errors is critical when gathering usage data from multiple AI services. In the steipete/CodexBar repository, the scanning pipeline implements a typed-error-per-provider strategy combined with centralized error aggregation to maintain code maintainability and UI stability. This article examines how to handle errors in CodexBar scanning by analyzing the source code architecture from low-level probes to high-level SwiftUI updates.

Provider-Specific Error Enums

Every probe in CodexBar defines a dedicated error enum that conforms to LocalizedError, Sendable, and often Equatable. This typed approach ensures exhaustive error handling and human-readable descriptions without fragile string matching.

For example, the Zed provider defines ZedStatusProbeError in Sources/CodexBarCore/Providers/Zed/ZedStatusProbe.swift:

public enum ZedStatusProbeError: LocalizedError, Sendable, Equatable {
    case unauthorized
    case notSignedIn
    case invalidServerURL
    case parseFailed(String)
    case apiError(Int, String)
    // Additional cases...
}

Similarly, the Windsurf provider implements WindsurfStatusProbeError in Sources/CodexBarCore/Providers/Windsurf/WindsurfStatusProbe.swift, while Claude uses ClaudeUsageError in Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift.

These enums categorize failures into distinct types:

  • Network errors: URLError(.cannotConnectToHost), CancellationError
  • Authentication failures: unauthorized, notSignedIn, invalidCookie
  • Parsing issues: parseFailed(String), apiError(Int, String)
  • Configuration problems: invalidEndpointOverride, untrustedServerConfiguration
  • Quota limitations: quotaExceeded, usageUnavailable

Because each enum adopts LocalizedError, the errorDescription property provides consistent, user-friendly messaging that the UI can display directly.

Centralized Error Orchestration

While individual probes throw typed errors, the ProviderFetchPlan and ProviderCandidateRetryRunner classes manage centralized error aggregation and retry logic.

ProviderFetchPlan

Located in Sources/CodexBarCore/Providers/ProviderFetchPlan.swift, this class defines the sequence of fetchers for a given provider and wraps individual errors in ProviderFetchError. This thin wrapper also conforms to LocalizedError, preserving human-readable descriptions while unifying the error type for the orchestration layer.

ProviderCandidateRetryRunner

Defined in Sources/CodexBarCore/Providers/ProviderCandidateRetryRunner.swift, this runner executes candidate fetchers with exponential backoff. It distinguishes between recoverable errors (temporary network glitches that trigger retries) and irrecoverable errors (malformed API keys that short-circuit the scan). Irrecoverable failures are immediately surfaced to the UI via Issue.record(_:) entries.

Propagating Errors to the UI

The scanning pipeline returns Result<ProbeData, ProviderFetchError> to the StatusItemController, which bridges errors to the SwiftUI interface.

When a failure occurs, the controller performs three actions:

  1. Calls error.localizedDescription (leveraging LocalizedError conformance) to obtain a friendly string
  2. Stores the error in the @Observable model StatusItemState, triggering automatic SwiftUI updates
  3. Optionally activates UI hints, such as flashing warning icons, based on error severity

This architecture enables context-specific UI actions. For instance, an invalidEndpointOverride error can trigger an "Open Settings" button, while network errors might display a "Retry" option.

Testing Error Handling Paths

Every error enum includes comprehensive unit tests to prevent regression. The test suite verifies that the orchestration layer correctly propagates each failure type.

Key test files include:

These tests ensure that future refactors cannot silently swallow failure cases or break error propagation chains.

Code Examples

Handling Provider-Specific Errors

When invoking a probe directly, catch the specific error type for granular handling:

do {
    let result = try await ZedStatusProbe().scan()
    // Update UI with successful data
} catch let error as ZedStatusProbeError {
    logger.error("Zed probe failed: \(error.localizedDescription)")
    statusItem.showError(error) // Updates UI badge
} catch {
    logger.error("Unexpected error: \(error)")
    statusItem.showGenericError()
}

Using ProviderFetchPlan for Centralized Handling

For coordinated fetching with automatic retry logic:

let plan = ProviderFetchPlan(provider: .zed, candidates: [.statusProbe, .tokenFetcher])
do {
    let data = try await plan.run()
    statusItem.update(with: data)
} catch let fetchError as ProviderFetchError {
    // All provider-specific errors wrapped uniformly
    statusItem.showError(fetchError)
}

SwiftUI Error Display

The observable state model enables reactive error presentation:

@Observable final class StatusItemState {
    var errorMessage: String? = nil
    // ...
}

// In the view:
if let message = state.errorMessage {
    Text(message)
        .foregroundColor(.red)
        .font(.caption)
}

Summary

  • Type safety: Every provider defines a specific error enum (e.g., ZedStatusProbeError, ClaudeUsageError) conforming to LocalizedError for exhaustive handling and human-readable descriptions.
  • Centralized orchestration: ProviderFetchPlan and ProviderCandidateRetryRunner manage retries, distinguish recoverable from irrecoverable errors, and wrap failures in ProviderFetchError.
  • UI integration: The StatusItemController propagates errors through StatusItemState, enabling SwiftUI to display context-specific actions and status badges.
  • Test coverage: Comprehensive suites in ZedStatusProbeTests.swift, WindsurfStatusProbeTests.swift, and ProviderCandidateRetryRunnerTests.swift verify error propagation using #expect(throws:) assertions.

Frequently Asked Questions

What error types does CodexBar use for provider scanning?

CodexBar uses provider-specific enums such as ZedStatusProbeError, WindsurfStatusProbeError, and ClaudeUsageError. Each enum conforms to LocalizedError, Sendable, and Equatable to provide type-safe error handling and human-readable descriptions for network, authentication, parsing, and configuration failures.

How does CodexBar handle retry logic when a scan fails?

The ProviderCandidateRetryRunner class in Sources/CodexBarCore/Providers/ProviderCandidateRetryRunner.swift implements exponential backoff for recoverable errors like temporary network glitches. Irrecoverable errors such as invalid API keys short-circuit immediately and propagate to the UI without retry attempts.

Can the UI display different actions based on specific error types?

Yes. Because all errors are typed enums conforming to LocalizedError, the StatusItemController can inspect specific cases (e.g., invalidEndpointOverride) and trigger context-appropriate actions like "Open Settings" buttons. This avoids fragile string-matching and ensures type-safe UI logic.

Where are the error handling tests located in the CodexBar repository?

Error handling tests are distributed across several files: Tests/CodexBarTests/ZedStatusProbeTests.swift for Zed-specific errors, Tests/CodexBarTests/WindsurfStatusProbeTests.swift for SQLite-related failures, and Tests/CodexBarTests/ProviderCandidateRetryRunnerTests.swift for retry logic validation. These use Swift Testing's #expect(throws:) syntax to verify correct error propagation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →