Unified · Split
| @@ -1231,7 +1231,14 @@ extension UsageStore { · 1230 unmodified lines | ||
| 1231 | 1231 | claudeCookieHeader: String, |
| 1232 | 1232 | keepCLISessionsAlive: Bool) async -> String |
| 1233 | 1233 | { |
| 1234 | − await self.runWithTimeout(seconds: 15) { | |
| 1234 | + struct OAuthDebugProbe: Sendable { | |
| 1235 | + let hasCredentials: Bool | |
| 1236 | + let ownerRawValue: String | |
| 1237 | + let sourceRawValue: String | |
| 1238 | + let isExpired: Bool | |
| 1239 | + } | |
| 1240 | + | |
| 1241 | + return await self.runWithTimeout(seconds: 15) { | |
| 1235 | 1242 | var lines: [String] = [] |
| 1236 | 1243 | let manualHeader = claudeCookieSource == .manual |
| 1237 | 1244 | ? CookieHeaderNormalizer.normalize(claudeCookieHeader) |
| @@ -1241,12 +1248,20 @@ extension UsageStore { · 3 unmodified lines | ||
| 1241 | 1248 | } else { |
| 1242 | 1249 | ClaudeWebAPIFetcher.hasSessionKey(browserDetection: self.browserDetection) { msg in lines.append(msg) } |
| 1243 | 1250 | } |
| 1244 | − // Don't prompt for keychain access during debug dump | |
| 1245 | − let oauthRecord = try? ClaudeOAuthCredentialsStore.loadRecord( | |
| 1246 | − allowKeychainPrompt: false, | |
| 1247 | − respectKeychainPromptCooldown: true, | |
| 1248 | − allowClaudeKeychainRepairWithoutPrompt: false) | |
| 1249 | − let hasOAuthCredentials = oauthRecord?.credentials.scopes.contains("user:profile") == true | |
| 1251 | + // Run potentially blocking keychain probes off MainActor so debug dumps don't stall UI rendering. | |
| 1252 | + let oauthProbe = await Task.detached(priority: .utility) { | |
| 1253 | + // Don't prompt for keychain access during debug dump. | |
| 1254 | + let oauthRecord = try? ClaudeOAuthCredentialsStore.loadRecord( | |
| 1255 | + allowKeychainPrompt: false, | |
| 1256 | + respectKeychainPromptCooldown: true, | |
| 1257 | + allowClaudeKeychainRepairWithoutPrompt: false) | |
| 1258 | + return OAuthDebugProbe( | |
| 1259 | + hasCredentials: oauthRecord?.credentials.scopes.contains("user:profile") == true, | |
| 1260 | + ownerRawValue: oauthRecord?.owner.rawValue ?? "none", | |
| 1261 | + sourceRawValue: oauthRecord?.source.rawValue ?? "none", | |
| 1262 | + isExpired: oauthRecord?.credentials.isExpired ?? false) | |
| 1263 | + }.value | |
| 1264 | + let hasOAuthCredentials = oauthProbe.hasCredentials | |
| 1250 | 1265 | let hasClaudeBinary = ClaudeOAuthDelegatedRefreshCoordinator.isClaudeCLIAvailable() |
| 1251 | 1266 | let delegatedCooldownSeconds = ClaudeOAuthDelegatedRefreshCoordinator.cooldownRemainingSeconds() |
| 1252 | 1267 | |
| @@ -1265,9 +1280,9 @@ extension UsageStore { · 12 unmodified lines | ||
| 1265 | 1280 | } |
| 1266 | 1281 | lines.append("hasSessionKey=\(hasKey)") |
| 1267 | 1282 | lines.append("hasOAuthCredentials=\(hasOAuthCredentials)") |
| 1268 | − lines.append("oauthCredentialOwner=\(oauthRecord?.owner.rawValue ?? "none")") | |
| 1269 | − lines.append("oauthCredentialSource=\(oauthRecord?.source.rawValue ?? "none")") | |
| 1270 | − lines.append("oauthCredentialExpired=\(oauthRecord?.credentials.isExpired ?? false)") | |
| 1283 | + lines.append("oauthCredentialOwner=\(oauthProbe.ownerRawValue)") | |
| 1284 | + lines.append("oauthCredentialSource=\(oauthProbe.sourceRawValue)") | |
| 1285 | + lines.append("oauthCredentialExpired=\(oauthProbe.isExpired)") | |
| 1271 | 1286 | lines.append("delegatedRefreshCLIAvailable=\(hasClaudeBinary)") |
| 1272 | 1287 | lines.append("delegatedRefreshCooldownActive=\(delegatedCooldownSeconds != nil)") |
| 1273 | 1288 | if let delegatedCooldownSeconds { |