import Foundation final class CodexCLILaunchGate: @unchecked Sendable { static let shared = CodexCLILaunchGate() static let cooldown: TimeInterval = 30 * 60 private struct Entry { let message: String let expiresAt: Date } private let lock = NSLock() private var entries: [String: Entry] = [:] func backgroundSkipMessage( binary: String, now: Date = Date(), interaction: ProviderInteraction = ProviderInteractionContext.current) -> String? { guard interaction == .background else { return nil } self.lock.lock() defer { self.lock.unlock() } guard let entry = self.entries[binary] else { return nil } guard entry.expiresAt > now else { self.entries.removeValue(forKey: binary) return nil } return entry.message } @discardableResult func recordLaunchFailure(binary: String, message: String, now: Date = Date()) -> String? { guard Self.shouldThrottleLaunchFailure(message) else { return nil } let throttled = Self.throttledMessage(binary: binary, originalMessage: message) let entry = Entry( message: throttled, expiresAt: now.addingTimeInterval(Self.cooldown)) self.lock.lock() self.entries[binary] = entry self.lock.unlock() return throttled } func resetForTesting() { self.lock.lock() self.entries.removeAll() self.lock.unlock() } static func shouldThrottleLaunchFailure(_ message: String) -> Bool { let lower = message.lowercased() if lower.contains("openpty") || lower.contains("write to pty") || lower.contains("app shutdown") { return false } return true } private static func throttledMessage(binary: String, originalMessage: String) -> String { "Codex CLI launch failed; background refresh is paused for 30 minutes. " + "Reinstall or unblock `\(binary)` in macOS security settings, then refresh manually. " + "Last error: \(originalMessage)" }}
import Foundation
final class CodexCLILaunchGate: @unchecked Sendable {
static let shared = CodexCLILaunchGate()
static let cooldown: TimeInterval = 30 * 60
private struct Entry {
let message: String
let expiresAt: Date
}
private let lock = NSLock()
private var entries: [String: Entry] = [:]
func backgroundSkipMessage(
binary: String,
now: Date = Date(),
interaction: ProviderInteraction = ProviderInteractionContext.current) -> String?
{
guard interaction == .background else { return nil }
self.lock.lock()
defer { self.lock.unlock() }
guard let entry = self.entries[binary] else { return nil }
guard entry.expiresAt > now else {
self.entries.removeValue(forKey: binary)
return nil
}
return entry.message
}
@discardableResult
func recordLaunchFailure(binary: String, message: String, now: Date = Date()) -> String? {
guard Self.shouldThrottleLaunchFailure(message) else { return nil }
let throttled = Self.throttledMessage(binary: binary, originalMessage: message)
let entry = Entry(
message: throttled,
expiresAt: now.addingTimeInterval(Self.cooldown))
self.lock.lock()
self.entries[binary] = entry
self.lock.unlock()
return throttled
}
func resetForTesting() {
self.lock.lock()
self.entries.removeAll()
self.lock.unlock()
}
static func shouldThrottleLaunchFailure(_ message: String) -> Bool {
let lower = message.lowercased()
if lower.contains("openpty") ||
lower.contains("write to pty") ||
lower.contains("app shutdown")
{
return false
}
return true
}
private static func throttledMessage(binary: String, originalMessage: String) -> String {
"Codex CLI launch failed; background refresh is paused for 30 minutes. " +
"Reinstall or unblock `\(binary)` in macOS security settings, then refresh manually. " +
"Last error: \(originalMessage)"
}
}