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 @@ -9,6 +9,7 @@

### Fixed

- Grok: send a nonempty billing request while preserving legacy monthly usage and leaving unknown percentages unchanged (#3336). Thanks @CharlieLZ!
- CLI: show each host's source snapshot timestamp in SSH cost text reports, making stale remote totals visible without changing JSON output (#3765). Thanks @A-l-an!
- Claude/Vertex costs: prevent crashes from oversized local history values, preserving valid token components and finite spend when an aggregate exceeds the cache's numeric range; preserve retained Codex history during the upgrade.
- Linux quotas: show measured provider-specific windows, omit synthetic or unmeasured quota, and keep scoped identities private in IPC without colliding with notification state (#3785). Thanks @jsonMartin!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public enum GrokWebBillingFetcher {
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.timeoutInterval = Self.requestTimeoutSeconds
request.httpBody = Data([0x00, 0x00, 0x00, 0x00, 0x00])
request.httpBody = Data([0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00])
if let authorizationHeader {
request.setValue(authorizationHeader, forHTTPHeaderField: "Authorization")
}
Expand Down
64 changes: 64 additions & 0 deletions Tests/CodexBarTests/GrokAccountContextTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import Testing
@testable import CodexBar
@testable import CodexBarCore

struct GrokAccountContextTests {
Expand Down Expand Up @@ -265,6 +266,69 @@ struct GrokAccountContextTests {
#expect(calls.value == (stage == "proxy" ? ["proxy"] : ["proxy", stage]))
}

@Test
func `missing RPC billing retains local tokens through the proxy fallback`() async throws {
let fixture = try GrokAccountFixture()
defer { fixture.remove() }
try fixture.write(account: "a")
let context = fixture.context(sourceMode: .auto)
let binary = try #require(context.env["GROK_CLI_PATH"])
let script = """
#!/bin/sh
if [ "$1" = "--version" ]; then
printf '%s\\n' 'grok synthetic-version'
exit 0
fi
IFS= read -r initialize_request
printf '%s\\n' '{"jsonrpc":"2.0","id":1,"result":{}}'
IFS= read -r billing_request || exit 0
printf '%s\\n' '{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found"}}'
"""
try script.write(toFile: binary, atomically: true, encoding: .utf8)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: binary)
let session = fixture.home.appendingPathComponent("sessions/project/session", isDirectory: true)
try FileManager.default.createDirectory(at: session, withIntermediateDirectories: true)
try Data("""
{"totalTokensBeforeCompaction":40,"contextTokensUsed":2,"primaryModelId":"example-model"}
""".utf8).write(to: session.appendingPathComponent("signals.json"))
var web = GrokWebFetchStrategy()
web.remainingResetsLookup = { _, _, _ in .empty }
let oauth = GrokOAuthFetchStrategy(
proxyBilling: { _ in GrokWebBillingSnapshot(usedPercent: 37, resetsAt: nil) },
grpcBilling: { _ in
Issue.record("Known proxy usage does not need gRPC enrichment")
throw GrokWebBillingError.invalidResponse
},
webStrategy: web,
settingsTier: { _ in nil })
let pipeline = ProviderFetchPipeline(resolveStrategies: { _ in [GrokCLIFetchStrategy(), oauth] })
let outcome = await pipeline.fetch(context: context, provider: .grok)
let result = try outcome.result.get()

#expect(outcome.attempts.count == 2)
#expect(outcome.attempts.first?.wasAvailable == true)
#expect(outcome.attempts.first?.errorDescription?.contains("Method not found") == true)
#expect(result.sourceLabel == "grok-cli-proxy")
#expect(result.usage.primary?.usedPercent == 37)
let history = try #require(result.usage.costUsage)
#expect(history.last30DaysTokens == 42)
#expect(history.daily.map(\.totalTokens) == [42])
#expect(history.last30DaysCostUSD == nil)
#expect(history.costProvenance == .unknown)
let model = SpendDashboardModel.build(
inputs: [.init(provider: .grok, displayName: "Grok", snapshot: history)],
requestedDays: 30,
now: history.updatedAt)
let shared = try #require(ShareStatsBuilder.make(model: model))
#expect(shared.providers.map(\.provider) == [.grok])
#expect(shared.providers.first?.totalTokens == 42)
#expect(shared.providers.first?.estimatedCost == nil)
// Usage JSON intentionally omits live-only history; its absence is not evidence of lost app data.
let data = try JSONEncoder().encode(result.usage)
let json = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(json["costUsage"] == nil)
}

private static func expectAccountA(_ credentials: GrokCredentials?) {
#expect(credentials?.accessToken == "fake-token-a")
#expect(credentials?.refreshToken == "fake-refresh-a")
Expand Down
8 changes: 7 additions & 1 deletion Tests/CodexBarTests/GrokWebBillingFetcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ struct GrokWebBillingFetcherTests {
endpoint: endpoint)

#expect(GrokWebBillingStubURLProtocol.requests.count == 1)
#expect(GrokWebBillingStubURLProtocol.requestBodies == [Data([0x00, 0x00, 0x00, 0x00, 0x00])])
#expect(GrokWebBillingStubURLProtocol.requestBodies == [Data([0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00])])
#expect(snapshot.usedPercent == 55.5)
#expect(snapshot.resetsAt == Date(timeIntervalSince1970: TimeInterval(reset)))
}
Expand Down Expand Up @@ -814,6 +814,10 @@ struct GrokWebBillingFetcherTests {

#expect(attempts.current() == 2)
#expect(GrokWebBillingStubURLProtocol.requests.count == 2)
#expect(GrokWebBillingStubURLProtocol.requests.allSatisfy { $0.timeoutInterval == 15 })
#expect(GrokWebBillingStubURLProtocol.requestBodies == Array(
repeating: Data([0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00]),
count: 2))
#expect(snapshot.usedPercent == 25)
#expect(snapshot.resetsAt == Date(timeIntervalSince1970: TimeInterval(reset)))
}
Expand Down Expand Up @@ -933,6 +937,7 @@ extension GrokWebBillingFetcherTests {
endpoint: endpoint)

#expect(snapshot.usedPercent == 9)
#expect(GrokWebBillingStubURLProtocol.requestBodies == [Data([0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00])])
}

@Test
Expand Down Expand Up @@ -968,6 +973,7 @@ extension GrokWebBillingFetcherTests {
endpoint: endpoint)

#expect(snapshot.usedPercent == 9)
#expect(GrokWebBillingStubURLProtocol.requestBodies == [Data([0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00])])
}

@Test
Expand Down
17 changes: 16 additions & 1 deletion docs/grok.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,16 @@ The grok.com billing gRPC-web endpoint remains a best-effort fallback.
that omit `subscription_tier_display` all drop the plan overlay and fall
back to the OIDC SuperGrok label. There is no process-lifetime tier cache.
4) **grok.com billing gRPC-web fallback** (best-effort)
- POSTs an empty gRPC-web protobuf request to
- POSTs `GetGrokCreditsConfigRequest { exclude_legacy_monthly_usage: false }`
(gRPC-web binary frame `00 00 00 00 02 08 00`) to
`https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig`.
Explicit false preserves the default billing semantics while supplying a
nonempty message for servers that reject an empty frame with gRPC status 13
(`Missing request message.`). Field 1 is a boolean, not a period selector;
`08 02` would enable exclusion of legacy monthly usage. The public
[billing descriptor](https://cdn.grok.com/_next/static/chunks/32g78bk5hhe1q.js)
was checked on September 21, 2026. No response-percentage inference changes
are required by this encoding; affected-account recovery remains unverified.
- This endpoint now requires the browser-held Web Key Exchange (WKE) keypair.
Cookie-only authentication can fail with gRPC status 16 and
`no-credentials`; signing in through Chrome alone cannot provide that proof
Expand Down Expand Up @@ -267,6 +275,13 @@ dollars. Local session scans run on the dedicated background usage-scan queue;
menu cards and spend views reuse the already-published snapshot instead of
walking the session directory whenever they render.

`costUsage` is live-only data and is intentionally omitted from `codexbar usage`
JSON and persisted usage snapshots. Its absence in JSON does not establish that
Usage & Spend lost the in-memory local token history. In Auto mode, an RPC
`-32601` failure advances to the proxy/web strategy, which scans local sessions
and attaches the token history to its successful quota snapshot. Local signals
remain token-only; they do not establish completed-turn counts or dollar spend.

## Menu bar appearance

Grok quota icons show a visor and twin antennae in both single- and two-meter layouts.
Expand Down
Loading