Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Grok: add an Auto / Grok CLI / SuperGrok OAuth / Browser cookies source picker, support pasted SuperGrok bearers and grok.com cookies in token accounts, and open `~/.grok/auth.json` from Open token file (#3010). Thanks @oakimov!

### Fixed
- Codex: classify app-server request timeouts before terminating the subprocess, so timeouts no longer surface as misleading EOF/malformed-response errors (#3022). Thanks @Chipagosfinest!
- OpenCode Go: surface expired selected session tokens instead of silently replacing failed server usage with local quota estimates (#2993). Thanks @Niclassslua!
- Claude spend: price bare first-party model IDs from their models.dev vendor catalog, preserve explicit routes, and leave ambiguous cross-vendor matches unpriced (#3002). Thanks @Yuxin-Qiao!
- Menu: prevent the system menu highlight from painting behind provider detail cards on macOS 27 beta (#2998). Thanks @Tan1103!
Expand Down
36 changes: 23 additions & 13 deletions Sources/CodexBarCore/UsageFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,11 @@ enum RPCWireError: Error, LocalizedError {
}
}

private enum RPCRequestRaceResult<Value: Sendable>: Sendable {
case value(Value)
case timedOut
}

/// RPC helper used on background tasks; safe because we confine it to the owning task.
private final class CodexRPCClient: @unchecked Sendable {
// Provider-specific by design: Codex RPC owns its dedicated subprocess log category.
Expand Down Expand Up @@ -1007,24 +1012,29 @@ private final class CodexRPCClient: @unchecked Sendable {
method: String,
body: @escaping @Sendable () async throws -> T) async throws -> T
{
try await withThrowingTaskGroup(of: T.self) { group in
try await withThrowingTaskGroup(of: RPCRequestRaceResult<T>.self) { group in
group.addTask {
try await body()
try await .value(body())
}
group.addTask { [weak self] in
group.addTask {
try await Task.sleep(for: .seconds(seconds))
self?.terminateProcessForTimeout(method: method)
throw RPCWireError.timeout(method: method)
return .timedOut
Comment on lines +1015 to +1021

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add focused coverage for the timeout race

This changes the concurrency ordering that distinguishes RPCWireError.timeout from the prior malformed-EOF result, but the commit only adjusts an architecture-test line number; the existing Codex RPC tests never stall a request and assert the resulting error classification. Add a synthetic app-server test that withholds a reply until the deadline and verifies that RPCWireError.timeout wins, otherwise the specific race fixed here can regress unnoticed.

AGENTS.md reference: AGENTS.md:L3-L5

Useful? React with 👍 / 👎.

}
do {
guard let result = try await group.next() else {
throw RPCWireError.timeout(method: method)
}
group.cancelAll()
return result
} catch {

guard let result = try await group.next() else {
group.cancelAll()
throw error
throw RPCWireError.timeout(method: method)
}
group.cancelAll()

switch result {
case let .value(value):
return value
case .timedOut:
// Terminating the process closes stdout. Classify that expected EOF as a
// timeout by selecting the timer before requesting process termination.
self.terminateProcessForTimeout(method: method)
throw RPCWireError.timeout(method: method)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1503,7 +1503,7 @@ struct ProviderArchitectureGatekeeperTests {
reason: "This tagged diagnostic payload encodes MiniMax details under the matching wire key."),
SuppressedProviderReference(
path: "Sources/CodexBarCore/UsageFetcher.swift",
line: 1480,
line: 1490,
anchor: "providerID: .codex,",
expectedProviderIDs: ["codex"],
reason: "This provider-specific core branch passes its already-selected identity to a shared helper."),
Expand Down