History

fix(server): usage no longer double-counts forked Codex sessions (#5887)

Theo Browne committed 3 months ago

Commit 0d388661 parent f993fa13 files · +159 −3Browse files

Showing the changes to apps/server/src/usage/usageTranscripts.test.ts (1 of 3 files). Show all

Unified · Split

apps/server/src/usage/usageTranscripts.test.ts+100−0
@@ -134,6 +134,106 @@ describe("parseCodexLine", () => { · 133 unmodified lines
134 parseCodexLine(turnContext, state);134 parseCodexLine(turnContext, state);
135 expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).not.toBeNull();135 expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).not.toBeNull();
136 });136 });
137
138 // A forked/subagent rollout opens with the parent's history copied in and
139 // every line re-stamped to the fork instant, then the ancestors' session
140 // metas. Counting those again multiplied usage ~1.85x on real data (#5758).
141 describe("forked rollouts", () => {
142 const meta = (overrides: {
143 id: string;
144 timestamp: string;
145 forkedFromId?: string;
146 spawnParentId?: string;
147 }) =>
148 JSON.stringify({
149 type: "session_meta",
150 timestamp: overrides.timestamp,
151 payload: {
152 type: "session_meta",
153 id: overrides.id,
154 ...(overrides.forkedFromId === undefined
155 ? {}
156 : { forked_from_id: overrides.forkedFromId }),
157 ...(overrides.spawnParentId === undefined
158 ? {}
159 : {
160 source: {
161 subagent: { thread_spawn: { parent_thread_id: overrides.spawnParentId } },
162 },
163 }),
164 },
165 });
166 const stamped = (timestamp: string, line: string) => {
167 const parsed = JSON.parse(line) as { timestamp: string };
168 parsed.timestamp = timestamp;
169 return JSON.stringify(parsed);
170 };
171
172 it("keeps the child session id over copied ancestor metas", () => {
173 const state = initialCodexScanState();
174 parseCodexLine(meta({ id: "child", timestamp: "2026-08-01T05:00:00.000Z" }), state);
175 parseCodexLine(meta({ id: "parent", timestamp: "2026-08-01T05:00:00.000Z" }), state);
176 parseCodexLine(turnContext, state);
177 const record = parseCodexLine(tokenCount(100, 0, 10, 0), state);
178
179 expect(record?.sessionId).toBe("child");
180 });
181
182 it("drops the re-stamped copied burst and keeps the first real event", () => {
183 const state = initialCodexScanState();
184 const forkInstant = "2026-08-01T05:00:00.000Z";
185 parseCodexLine(meta({ id: "child", timestamp: forkInstant, forkedFromId: "parent" }), state);
186 parseCodexLine(meta({ id: "parent", timestamp: forkInstant }), state);
187 parseCodexLine(stamped(forkInstant, turnContext), state);
188
189 // Copied history: written in one burst at the fork instant.
190 expect(
191 parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state),
192 ).toBeNull();
193 expect(
194 parseCodexLine(stamped("2026-08-01T05:00:00.002Z", tokenCount(200, 0, 20, 0)), state),
195 ).toBeNull();
196
197 // The child's first genuine turn lands seconds later and must count.
198 const real = parseCodexLine(
199 stamped("2026-08-01T05:00:06.000Z", tokenCount(300, 0, 30, 0)),
200 state,
201 );
202 expect(real).not.toBeNull();
203 expect(real?.totals.outputTokens).toBe(30);
204
205 // Suppression never restarts, even for closely spaced later events.
206 const next = parseCodexLine(
207 stamped("2026-08-01T05:00:06.100Z", tokenCount(400, 0, 40, 0)),
208 state,
209 );
210 expect(next).not.toBeNull();
211 });
212
213 it("recognizes subagent spawns without forked_from_id", () => {
214 const state = initialCodexScanState();
215 const spawnInstant = "2026-08-01T05:00:00.000Z";
216 parseCodexLine(
217 meta({ id: "child", timestamp: spawnInstant, spawnParentId: "parent" }),
218 state,
219 );
220 parseCodexLine(stamped(spawnInstant, turnContext), state);
221 expect(
222 parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state),
223 ).toBeNull();
224 });
225
226 it("does not suppress anything in a rollout that is not a fork", () => {
227 const state = initialCodexScanState();
228 parseCodexLine(meta({ id: "root", timestamp: "2026-08-01T05:00:00.000Z" }), state);
229 parseCodexLine(stamped("2026-08-01T05:00:00.100Z", turnContext), state);
230 const record = parseCodexLine(
231 stamped("2026-08-01T05:00:00.200Z", tokenCount(100, 0, 10, 0)),
232 state,
233 );
234 expect(record).not.toBeNull();
235 });
236 });
137});237});
138 238
139describe("totalTokens", () => {239describe("totalTokens", () => {