codeburn/tests/codex-missing-cumulative.test.ts
Roy Tong 47b7441dd6
fix(codex): preserve distinct requests when cumulative usage is missing (#1264)
* fix(codex): keep token_count events in sessions that never write total_token_usage

* fix(codex): preserve distinct requests when cumulative usage is missing

* fix(codex): narrow missing-cumulative fix - byte-identical re-emissions still collapse (#257)

Real-data answer to the review's question: Codex does re-emit byte-identical
token_count events. Scanned 53 public rollout sessions (codeset-ai/
codeset-release-evals, 1313 token_count events): 603 (46%) are byte-identical
repeats of their predecessor (identical total AND last usage), 0 cumulative
monotonicity violations, 54 events carry no total_token_usage at all.

The missing-cumulative path now collapses a record whose info payload is
byte-identical to the previous event's (#257 semantics restored); only
records with differing payloads count as distinct requests. prevInfoIdentity
is persisted in the resume state. The #1088 invariant note is restored at
the dedupKey in adapted form; docs/providers/codex.md documents the
three-layer dedup and the accepted fork-cutoff trade-off.

* fix(codex): revert cache/parse version bumps, correct null-info docs per review

Re-verified on a fresh spread sample of codeset-release-evals (21 sessions,
404 events): exactly one info:null rate-limit ping per session, zero events
with info present but total_token_usage absent — matching the reviewer's
137k-event corpus. The missing-cumulative branch is a no-op on real data:

- CODEX_CACHE_VERSION stays 15 (no cold reparse for identical numbers; v16
  never shipped in a release)
- drop the -missing-cumulative-v1 parse suffix
- Quirks: null-info pings take the estimate path, not the dedup path; the
  earlier 54-of-1313 figure conflated them with partial-info events
- dedup layer 3 now documents the forkedFromId/#1383 sub-agent caveat

---------

Co-authored-by: Roy tong <tongxiarui@elliwooddeMacBook-Air.local>
2026-09-16 10:50:25 +02:00

58 lines
3.2 KiB
TypeScript

import { it, expect, vi } from 'vitest'
import { mkdtemp, writeFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { createCodexProvider } from '../src/providers/codex.js'
vi.mock('../src/codex-cache.js', async (original) => ({
...await original<typeof import('../src/codex-cache.js')>(),
readCachedCodexResults: async () => null,
readCodexResume: async () => null,
writeCachedCodexResults: async () => {},
}))
// Real-data basis (codeset-ai/codeset-release-evals, 53 sessions / 1313
// token_count events): 603 events are byte-identical repeats of their
// predecessor -- Codex re-emits token_count snapshots unchanged within a
// response -- while 54 events carry no total_token_usage at all. Both
// behaviors must be handled: identical repeats collapse (#257), distinct
// payloads without cumulative stay distinct.
for (const total of [undefined, null, {}]) {
it(`collapses byte-identical re-emitted records with cumulative ${JSON.stringify(total)}`, async () => {
const dir = await mkdtemp(join(tmpdir(), 'codeburn-missing-total-'))
try {
const path = join(dir, 'rollout-reemit.jsonl')
const identical = { last_token_usage: { input_tokens: 100, output_tokens: 20 }, total_token_usage: total }
const lines = [
{ type: 'session_meta', timestamp: '2026-09-01T10:00:00Z', payload: { session_id: 's', model: 'gpt-5.3-codex' } },
...[1, 2, 3].map(n => ({ type: 'event_msg', timestamp: `2026-09-01T10:00:0${n}Z`, payload: { type: 'token_count', info: identical } })),
]
await writeFile(path, lines.map(line => JSON.stringify(line)).join('\n') + '\n')
const parser = createCodexProvider(dir).createSessionParser({ path, project: 'test', provider: 'codex' }, new Set())
const calls = []
for await (const call of parser.parse()) calls.push(call)
expect(calls).toHaveLength(1)
expect(calls[0].outputTokens).toBe(20)
} finally { await rm(dir, { recursive: true, force: true }) }
})
it(`preserves distinct records with differing payloads when cumulative is ${JSON.stringify(total)}`, async () => {
const dir = await mkdtemp(join(tmpdir(), 'codeburn-missing-total-'))
try {
const path = join(dir, 'rollout-distinct.jsonl')
const lines = [
{ type: 'session_meta', timestamp: '2026-09-01T10:00:00Z', payload: { session_id: 's', model: 'gpt-5.3-codex' } },
...[0, 1, 2].map(n => ({ type: 'event_msg', timestamp: `2026-09-01T10:00:0${n + 1}Z`, payload: {
type: 'token_count', info: { last_token_usage: { input_tokens: 100 + n, output_tokens: 20 + n }, total_token_usage: total },
} })),
]
await writeFile(path, lines.map(line => JSON.stringify(line)).join('\n') + '\n')
const parser = createCodexProvider(dir).createSessionParser({ path, project: 'test', provider: 'codex' }, new Set())
const calls = []
for await (const call of parser.parse()) calls.push(call)
expect(calls).toHaveLength(3)
expect(calls.reduce((sum, call) => sum + call.outputTokens, 0)).toBe(63)
expect(new Set(calls.map(call => call.deduplicationKey)).size).toBe(3)
} finally { await rm(dir, { recursive: true, force: true }) }
})
}