diff --git a/src/infra/session-cost-usage.test.ts b/src/infra/session-cost-usage.test.ts index 387cd6417479..f2dcd2d64a3c 100644 --- a/src/infra/session-cost-usage.test.ts +++ b/src/infra/session-cost-usage.test.ts @@ -2753,4 +2753,28 @@ example loadSessionLogs({ sessionFile, limit: Number.POSITIVE_INFINITY }), ).resolves.toEqual([]); }); + + it("keeps the latest logs when transcript timestamps are out of order", async () => { + const root = await makeSessionCostRoot("session-logs-unsorted-limit"); + const sessionFile = path.join(root, "session.jsonl"); + const entries = [ + ["2026-02-12T10:03:00.000Z", "third"], + ["2026-02-12T10:01:00.000Z", "first"], + ["2026-02-12T10:04:00.000Z", "fourth"], + ["2026-02-12T10:02:00.000Z", "second"], + ].map(([timestamp, content]) => ({ + type: "message", + timestamp, + message: { role: "user", content }, + })); + await fs.writeFile( + sessionFile, + entries.map((entry) => JSON.stringify(entry)).join("\n"), + "utf-8", + ); + + const logs = await loadSessionLogs({ sessionFile, limit: 2 }); + + expect(logs?.map((log) => log.content)).toEqual(["third", "fourth"]); + }); }); diff --git a/src/infra/session-cost-usage.ts b/src/infra/session-cost-usage.ts index cd3e97ea95d5..c8f074dec0d7 100644 --- a/src/infra/session-cost-usage.ts +++ b/src/infra/session-cost-usage.ts @@ -2525,6 +2525,8 @@ export async function loadSessionLogs(params: { } } const limit = params.limit ?? 50; + const boundedLimit = Number.isInteger(limit); + const retentionLimit = limit * 2; const resolveCost = createUsageCostResolver(params.config); for await (const parsed of readJsonlRecords(sessionFile)) { @@ -2656,15 +2658,25 @@ export async function loadSessionLogs(params: { tokens, cost, }); + // Timestamps can arrive out of order, so keep a bounded sorted window instead + // of relying on transcript append order or retaining the whole file. + if (boundedLimit && logs.length > retentionLimit) { + logs.sort((a, b) => a.timestamp - b.timestamp); + logs.splice(0, logs.length - limit); + } } catch { // Ignore malformed lines } } // Sort by timestamp and limit - const sortedLogs = logs.toSorted((a, b) => a.timestamp - b.timestamp); + if (boundedLimit) { + logs.sort((a, b) => a.timestamp - b.timestamp); + return logs.length > limit ? logs.slice(-limit) : logs; + } // Return most recent logs + const sortedLogs = logs.toSorted((a, b) => a.timestamp - b.timestamp); if (sortedLogs.length > limit) { return sortedLogs.slice(-limit); }