From 5799250b88a44a5f9a40ddb3c49f24f6b93b786f Mon Sep 17 00:00:00 2001 From: Alec Gutman <44984861+Chipagosfinest@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:06:31 -0700 Subject: [PATCH 1/5] Keep Claude login on auto source --- .../CodexBar/Providers/Claude/ClaudeLoginFlow.swift | 9 ++++++++- Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift diff --git a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift index 76002520ef..52b552276f 100644 --- a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift +++ b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift @@ -1,5 +1,11 @@ import CodexBarCore +enum ClaudeLoginFlowPolicy { + static func usageDataSourceAfterSuccessfulLogin(previous _: ClaudeUsageDataSource) -> ClaudeUsageDataSource { + .auto + } +} + @MainActor extension StatusItemController { func runClaudeLoginFlow() async -> Bool { @@ -21,7 +27,8 @@ extension StatusItemController { if case .success = result.outcome { let metadata = self.store.metadata(for: .claude) self.settings.setProviderEnabled(provider: .claude, metadata: metadata, enabled: true) - self.settings.claudeUsageDataSource = .oauth + self.settings.claudeUsageDataSource = ClaudeLoginFlowPolicy.usageDataSourceAfterSuccessfulLogin( + previous: self.settings.claudeUsageDataSource) self.postLoginNotification(for: .claude) return true } diff --git a/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift new file mode 100644 index 0000000000..e840d189cf --- /dev/null +++ b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift @@ -0,0 +1,12 @@ +import CodexBarCore +import Testing +@testable import CodexBar + +struct ClaudeLoginFlowPolicyTests { + @Test + func `successful Claude login keeps source on auto so fallback remains available`() { + for source in ClaudeUsageDataSource.allCases { + #expect(ClaudeLoginFlowPolicy.usageDataSourceAfterSuccessfulLogin(previous: source) == .auto) + } + } +} From b47070b4a8ab9746098e5f9131166943a9468eed Mon Sep 17 00:00:00 2001 From: Alec Gutman <44984861+Chipagosfinest@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:15:47 -0700 Subject: [PATCH 2/5] Update Claude login source docs --- docs/claude.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/claude.md b/docs/claude.md index cd497f371d..3c7331e715 100644 --- a/docs/claude.md +++ b/docs/claude.md @@ -79,7 +79,8 @@ Admin API key setup: - `seven_day_routines` / `seven_day_cowork` → Daily Routines extra window. - Claude Design/Omelette keys are ignored because Claude Design shares the main Claude usage limit. - `extra_usage` → Extra usage cost (monthly spend/limit). -- Successful OAuth login enables Claude and selects OAuth as the usage source. +- Successful OAuth login enables Claude and keeps the usage source on Auto so OAuth remains preferred when readable, + while CLI/Web fallback stays available when OAuth credentials are not usable. - Plan inference: `subscriptionType` is preferred when present; `rate_limit_tier` falls back to Max/Pro/Team/Enterprise. When a Max `rate_limit_tier` carries a usage multiplier (`default_claude_max_5x` / `default_claude_max_20x`), it is surfaced in the label as "Max 5x" / "Max 20x". From 5f346aafe08c4ddb08a52bbaa57c9c4bf7fd9450 Mon Sep 17 00:00:00 2001 From: Alec Gutman <44984861+Chipagosfinest@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:18:17 -0700 Subject: [PATCH 3/5] Preserve explicit Claude source selections --- Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift | 4 ++-- Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift | 4 ++-- docs/claude.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift index 52b552276f..4db8485dde 100644 --- a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift +++ b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift @@ -1,8 +1,8 @@ import CodexBarCore enum ClaudeLoginFlowPolicy { - static func usageDataSourceAfterSuccessfulLogin(previous _: ClaudeUsageDataSource) -> ClaudeUsageDataSource { - .auto + static func usageDataSourceAfterSuccessfulLogin(previous: ClaudeUsageDataSource) -> ClaudeUsageDataSource { + previous } } diff --git a/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift index e840d189cf..4ddb229ed4 100644 --- a/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift +++ b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift @@ -4,9 +4,9 @@ import Testing struct ClaudeLoginFlowPolicyTests { @Test - func `successful Claude login keeps source on auto so fallback remains available`() { + func `successful Claude login preserves selected source so auto fallback remains available`() { for source in ClaudeUsageDataSource.allCases { - #expect(ClaudeLoginFlowPolicy.usageDataSourceAfterSuccessfulLogin(previous: source) == .auto) + #expect(ClaudeLoginFlowPolicy.usageDataSourceAfterSuccessfulLogin(previous: source) == source) } } } diff --git a/docs/claude.md b/docs/claude.md index 3c7331e715..78ccb089a5 100644 --- a/docs/claude.md +++ b/docs/claude.md @@ -79,8 +79,8 @@ Admin API key setup: - `seven_day_routines` / `seven_day_cowork` → Daily Routines extra window. - Claude Design/Omelette keys are ignored because Claude Design shares the main Claude usage limit. - `extra_usage` → Extra usage cost (monthly spend/limit). -- Successful OAuth login enables Claude and keeps the usage source on Auto so OAuth remains preferred when readable, - while CLI/Web fallback stays available when OAuth credentials are not usable. +- Successful OAuth login enables Claude and preserves the selected usage source. With the default Auto source, OAuth + remains preferred when readable, while CLI/Web fallback stays available when OAuth credentials are not usable. - Plan inference: `subscriptionType` is preferred when present; `rate_limit_tier` falls back to Max/Pro/Team/Enterprise. When a Max `rate_limit_tier` carries a usage multiplier (`default_claude_max_5x` / `default_claude_max_20x`), it is surfaced in the label as "Max 5x" / "Max 20x". From 30a2454d402e04cba29f0d538510847a31918555 Mon Sep 17 00:00:00 2001 From: Alec Gutman <44984861+Chipagosfinest@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:49:58 -0700 Subject: [PATCH 4/5] Prove Claude login source persistence --- .../Providers/Claude/ClaudeLoginFlow.swift | 14 +++++- .../ClaudeLoginFlowPolicyTests.swift | 43 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift index 4db8485dde..b65527a788 100644 --- a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift +++ b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift @@ -1,4 +1,9 @@ import CodexBarCore +import Foundation + +typealias ClaudeLoginFlowRunner = ( + _ timeout: TimeInterval, + _ onPhaseChange: @escaping @Sendable (ClaudeLoginRunner.Phase) -> Void) async -> ClaudeLoginRunner.Result enum ClaudeLoginFlowPolicy { static func usageDataSourceAfterSuccessfulLogin(previous: ClaudeUsageDataSource) -> ClaudeUsageDataSource { @@ -9,6 +14,13 @@ enum ClaudeLoginFlowPolicy { @MainActor extension StatusItemController { func runClaudeLoginFlow() async -> Bool { + await self.runClaudeLoginFlow( + loginRunner: { timeout, onPhaseChange in + await ClaudeLoginRunner.run(timeout: timeout, onPhaseChange: onPhaseChange) + }) + } + + func runClaudeLoginFlow(loginRunner: ClaudeLoginFlowRunner) async -> Bool { let phaseHandler: @Sendable (ClaudeLoginRunner.Phase) -> Void = { [weak self] phase in Task { @MainActor in switch phase { @@ -17,7 +29,7 @@ extension StatusItemController { } } } - let result = await ClaudeLoginRunner.run(timeout: 120, onPhaseChange: phaseHandler) + let result = await loginRunner(120, phaseHandler) guard !Task.isCancelled else { return false } self.loginPhase = .idle self.presentClaudeLoginResult(result) diff --git a/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift index 4ddb229ed4..d77875e1bb 100644 --- a/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift +++ b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift @@ -2,6 +2,8 @@ import CodexBarCore import Testing @testable import CodexBar +@MainActor +@Suite(.serialized) struct ClaudeLoginFlowPolicyTests { @Test func `successful Claude login preserves selected source so auto fallback remains available`() { @@ -9,4 +11,45 @@ struct ClaudeLoginFlowPolicyTests { #expect(ClaudeLoginFlowPolicy.usageDataSourceAfterSuccessfulLogin(previous: source) == source) } } + + @Test + func `successful Claude login controller flow preserves selected source and enables provider`() async throws { + let registry = ProviderRegistry.shared + let claudeMetadata = try #require(registry.metadata[.claude]) + + for source in ClaudeUsageDataSource.allCases { + let settings = testSettingsStore( + suiteName: "ClaudeLoginFlowPolicyTests-controller-\(source.rawValue)") + settings.statusChecksEnabled = false + settings.refreshFrequency = .manual + settings.providerDetectionCompleted = true + settings.claudeUsageDataSource = source + settings.setProviderEnabled(provider: .claude, metadata: claudeMetadata, enabled: false) + + let fetcher = UsageFetcher() + let store = UsageStore( + fetcher: fetcher, + browserDetection: BrowserDetection(cacheTTL: 0), + settings: settings) + + await withStatusItemControllerForTesting(store: store, settings: settings, fetcher: fetcher) { controller in + let didLogin = await controller.runClaudeLoginFlow { _, onPhaseChange in + onPhaseChange(.requesting) + await Task.yield() + onPhaseChange(.waitingBrowser) + await Task.yield() + return ClaudeLoginRunner.Result( + outcome: .success, + output: "Successfully logged in", + authLink: nil) + } + + #expect(didLogin) + #expect(controller.loginPhase == .idle) + } + + #expect(settings.claudeUsageDataSource == source) + #expect(settings.isProviderEnabledCached(provider: .claude, metadataByProvider: registry.metadata)) + } + } } From d2a656386f0831d9344991848b38a2d2e2f4bd15 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 12 Jul 2026 01:16:18 +0100 Subject: [PATCH 5/5] fix: preserve Claude usage source after login --- CHANGELOG.md | 1 + .../CodexBar/Providers/Claude/ClaudeLoginFlow.swift | 8 -------- Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift | 11 ++--------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9911681847..edb01a4381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Settings: split provider pane "Settings" sections into "Menu bar" and "Connection" so metric pickers and auth/cookie/source controls are grouped by topic. ### Fixed +- Claude login: preserve the selected usage source after OAuth sign-in so Auto mode can still fall back to CLI or web data when OAuth is unavailable. Thanks @Chipagosfinest! - Claude quotas: ignore synthetic no-session placeholders when tracking notifications, history, and reset events, preventing false restores and duplicate threshold or pace warnings while weekly usage continues updating. Thanks @vincent-peng! - German localization: label manual cookie-source and refresh options as “Manuell” instead of the handbook noun “Handbuch.” Thanks @fbrettnich! - Amp: parse the current percentage-based daily Amp Free usage output while preserving individual and workspace balances. Thanks @3kh0! diff --git a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift index b65527a788..29241390d0 100644 --- a/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift +++ b/Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift @@ -5,12 +5,6 @@ typealias ClaudeLoginFlowRunner = ( _ timeout: TimeInterval, _ onPhaseChange: @escaping @Sendable (ClaudeLoginRunner.Phase) -> Void) async -> ClaudeLoginRunner.Result -enum ClaudeLoginFlowPolicy { - static func usageDataSourceAfterSuccessfulLogin(previous: ClaudeUsageDataSource) -> ClaudeUsageDataSource { - previous - } -} - @MainActor extension StatusItemController { func runClaudeLoginFlow() async -> Bool { @@ -39,8 +33,6 @@ extension StatusItemController { if case .success = result.outcome { let metadata = self.store.metadata(for: .claude) self.settings.setProviderEnabled(provider: .claude, metadata: metadata, enabled: true) - self.settings.claudeUsageDataSource = ClaudeLoginFlowPolicy.usageDataSourceAfterSuccessfulLogin( - previous: self.settings.claudeUsageDataSource) self.postLoginNotification(for: .claude) return true } diff --git a/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift index d77875e1bb..eeba8ccb68 100644 --- a/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift +++ b/Tests/CodexBarTests/ClaudeLoginFlowPolicyTests.swift @@ -4,14 +4,7 @@ import Testing @MainActor @Suite(.serialized) -struct ClaudeLoginFlowPolicyTests { - @Test - func `successful Claude login preserves selected source so auto fallback remains available`() { - for source in ClaudeUsageDataSource.allCases { - #expect(ClaudeLoginFlowPolicy.usageDataSourceAfterSuccessfulLogin(previous: source) == source) - } - } - +struct ClaudeLoginFlowTests { @Test func `successful Claude login controller flow preserves selected source and enables provider`() async throws { let registry = ProviderRegistry.shared @@ -19,7 +12,7 @@ struct ClaudeLoginFlowPolicyTests { for source in ClaudeUsageDataSource.allCases { let settings = testSettingsStore( - suiteName: "ClaudeLoginFlowPolicyTests-controller-\(source.rawValue)") + suiteName: "ClaudeLoginFlowTests-controller-\(source.rawValue)") settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.providerDetectionCompleted = true