Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand.

enum CodexParserHash {
static let value = "97cf82ab7b18b255"
static let value = "514c63aac596993e"
}
Original file line number Diff line number Diff line change
Expand Up @@ -1693,8 +1693,15 @@ extension CostUsageScanner {
}
let cachedStandardTokens = standardTokensByDayModel[day]?[model]
let cachedPriorityTokens = priorityTokensByDayModel[day]?[model]
let standardCost = cachedStandardCost
let priorityCost = cachedPriorityCost
let (splitTokenTotal, splitTokenOverflow) = (cachedStandardTokens ?? 0)
.addingReportingOverflow(cachedPriorityTokens ?? 0)
// Fork-copied rows can inflate the per-file split maps; never publish a split
// that exceeds the canonical day/model total.
let splitIsUntrusted = splitTokenOverflow || splitTokenTotal > totalTokens
let standardCost = splitIsUntrusted ? nil : cachedStandardCost
let priorityCost = splitIsUntrusted ? nil : cachedPriorityCost
Comment on lines +1700 to +1702

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 Discard the surcharge when rejecting an inflated split

When copied priority rows make the split exceed the canonical token total, this marks the split costs as nil, but that newly activates the fallback at lines 1719–1723, which adds codexPrioritySurchargeNanosByDayModel to the base cost. That surcharge is generated from the same per-file priority rows as codexModeSplitMaps, so those copied rows can inflate it too; affected fork/subagent caches therefore continue to overreport cost even though their Standard/Fast fields disappear. The untrusted path should also discard the cached surcharge or recompute it from canonical ownership.

Useful? React with 👍 / 👎.

let standardTokens = splitIsUntrusted ? nil : cachedStandardTokens
let priorityTokens = splitIsUntrusted ? nil : cachedPriorityTokens
let splitTotalCost: Double? = if standardCost != nil || priorityCost != nil {
(standardCost ?? 0) + (priorityCost ?? 0)
} else {
Expand All @@ -1715,16 +1722,16 @@ extension CostUsageScanner {
{
cost = (cost ?? 0) + (Double(surchargeNanos) / Self.costScale)
}
let hasModeSplit = priorityCost != nil || cachedPriorityTokens != nil
let hasModeSplit = priorityCost != nil || priorityTokens != nil
breakdown.append(
CostUsageDailyReport.ModelBreakdown(
modelName: model,
costUSD: cost,
totalTokens: totalTokens,
standardCostUSD: hasModeSplit ? standardCost : nil,
priorityCostUSD: hasModeSplit ? priorityCost : nil,
standardTokens: hasModeSplit ? cachedStandardTokens : nil,
priorityTokens: hasModeSplit ? cachedPriorityTokens : nil))
standardTokens: hasModeSplit ? standardTokens : nil,
priorityTokens: hasModeSplit ? priorityTokens : nil))
if let cost {
dayCost += cost
dayCostSeen = true
Expand Down
127 changes: 127 additions & 0 deletions Tests/CodexBarTests/CostUsageScannerPriorityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,133 @@ struct CostUsageScannerPriorityTests {
#expect(abs((report.summary?.totalCostUSD ?? 0) - expected) < 0.000_000_001)
}

@Test
func `codex cached report drops inflated standard fast split when it exceeds canonical total`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }

let day = try env.makeLocalNoon(year: 2026, month: 8, day: 6)
let dayKey = CostUsageScanner.CostUsageDayRange.dayKey(from: day)
let cache = Self.makeSplitCodexCache(
dayKey: dayKey,
model: "gpt-5.5",
canonical: (input: 1000, cached: 0, output: 500),
split: (
standardTokens: 10_000_000,
priorityTokens: 5_000_000,
standardCostNanos: 10_000_000_000,
priorityCostNanos: 5_000_000_000),
baseCostNanos: 20_000_000)

let report = CostUsageScanner.buildCodexReportFromCache(
cache: cache,
range: CostUsageScanner.CostUsageDayRange(since: day, until: day))

// Canonical ownership is 1,500 tokens, but the copied fork split claims 15,000,000.
// The report must not publish that split or its inflated cost; it falls back to the
// canonical base cost of $0.02 (1,000 input + 500 output at gpt-5.5 rates).
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(breakdown.totalTokens == 1500)
#expect(abs((breakdown.costUSD ?? 0) - 0.02) < 1e-12)
#expect(breakdown.standardCostUSD == nil)
#expect(breakdown.priorityCostUSD == nil)
#expect(breakdown.standardTokens == nil)
#expect(breakdown.priorityTokens == nil)
#expect(abs((report.summary?.totalCostUSD ?? 0) - 0.02) < 1e-12)
}

@Test
func `codex cached report keeps valid standard fast split when tokens reconcile with canonical total`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }

let day = try env.makeLocalNoon(year: 2026, month: 8, day: 6)
let dayKey = CostUsageScanner.CostUsageDayRange.dayKey(from: day)
let cache = Self.makeSplitCodexCache(
dayKey: dayKey,
model: "gpt-5.5",
canonical: (input: 1000, cached: 0, output: 500),
split: (
standardTokens: 1000,
priorityTokens: 500,
standardCostNanos: 20_000_000,
priorityCostNanos: 10_000_000),
baseCostNanos: 20_000_000)

let report = CostUsageScanner.buildCodexReportFromCache(
cache: cache,
range: CostUsageScanner.CostUsageDayRange(since: day, until: day))

// A split that reconciles with canonical ownership keeps its exact cost and token values.
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(breakdown.totalTokens == 1500)
#expect(abs((breakdown.costUSD ?? 0) - 0.03) < 1e-12)
#expect(abs((breakdown.standardCostUSD ?? 0) - 0.02) < 1e-12)
#expect(abs((breakdown.priorityCostUSD ?? 0) - 0.01) < 1e-12)
#expect(breakdown.standardTokens == 1000)
#expect(breakdown.priorityTokens == 500)
#expect(abs((report.summary?.totalCostUSD ?? 0) - 0.03) < 1e-12)
}

@Test
func `codex cached report treats overflowing standard fast token sum as untrusted`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }

let day = try env.makeLocalNoon(year: 2026, month: 8, day: 6)
let dayKey = CostUsageScanner.CostUsageDayRange.dayKey(from: day)
let cache = Self.makeSplitCodexCache(
dayKey: dayKey,
model: "gpt-5.5",
canonical: (input: 1000, cached: 0, output: 500),
split: (
standardTokens: Int.max,
priorityTokens: 1,
standardCostNanos: 10_000_000_000,
priorityCostNanos: 5_000_000_000),
baseCostNanos: 20_000_000)

let report = CostUsageScanner.buildCodexReportFromCache(
cache: cache,
range: CostUsageScanner.CostUsageDayRange(since: day, until: day))

let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(breakdown.totalTokens == 1500)
#expect(abs((breakdown.costUSD ?? 0) - 0.02) < 1e-12)
#expect(breakdown.standardCostUSD == nil)
#expect(breakdown.priorityCostUSD == nil)
#expect(breakdown.standardTokens == nil)
#expect(breakdown.priorityTokens == nil)
}

private static func makeSplitCodexCache(
dayKey: String,
model: String,
canonical: (input: Int, cached: Int, output: Int),
split: (
standardTokens: Int,
priorityTokens: Int,
standardCostNanos: Int64,
priorityCostNanos: Int64),
baseCostNanos: Int64) -> CostUsageCache
{
var file = CostUsageFileUsage(
mtimeUnixMs: 1_751_000_000_000,
size: 1024,
days: [dayKey: [model: [canonical.input, canonical.cached, canonical.output]]])
file.codexCostCacheComplete = true
file.codexCostNanos = [dayKey: [model: baseCostNanos]]
file.codexStandardCostNanos = [dayKey: [model: split.standardCostNanos]]
file.codexPriorityCostNanos = [dayKey: [model: split.priorityCostNanos]]
file.codexStandardTokens = [dayKey: [model: split.standardTokens]]
file.codexPriorityTokens = [dayKey: [model: split.priorityTokens]]

var cache = CostUsageCache()
cache.days = [dayKey: [model: [canonical.input, canonical.cached, canonical.output]]]
cache.files = ["/sessions/2026-08-06/fork-copied.jsonl": file]
return cache
}

private func tokenCount(timestamp: String, input: Int, cached: Int, output: Int) -> [String: Any] {
[
"type": "event_msg",
Expand Down
Loading