diff --git a/src/shared/automation-run-retention.test.ts b/src/shared/automation-run-retention.test.ts index c405c43f37b..6b6c7ec73de 100644 --- a/src/shared/automation-run-retention.test.ts +++ b/src/shared/automation-run-retention.test.ts @@ -171,3 +171,25 @@ describe('nextAutomationRunNumber', () => { expect(runs.some((r) => r.runNumber === next)).toBe(false) }) }) + +it('does not inspect ordering timestamps when an automation is within its retention cap', () => { + let reads = 0 + const runs = Array.from({ length: 100 }, (_, index) => + run({ + id: `run-${index}`, + automationId: 'a' + }) + ) + for (const [index, entry] of runs.entries()) { + Object.defineProperty(entry, 'createdAt', { + get() { + reads += 1 + return (index * 37) % 100 + } + }) + } + const kept = pruneAutomationRuns(runs) + expect(kept).toHaveLength(100) + expect(kept.every((entry, index) => entry === runs[index])).toBe(true) + expect(reads).toBe(0) +}) diff --git a/src/shared/automation-run-retention.ts b/src/shared/automation-run-retention.ts index 3b0eae648ad..8fec7a07035 100644 --- a/src/shared/automation-run-retention.ts +++ b/src/shared/automation-run-retention.ts @@ -15,7 +15,9 @@ export function pruneAutomationRuns( for (const automationRuns of Map.groupBy(finalRuns, (run) => run.automationId).values()) { // Why: `createdAt` is the append time; `scheduledFor` breaks ties so runs // minted in the same millisecond drop in a stable, reproducible order. - automationRuns.sort((a, b) => b.createdAt - a.createdAt || b.scheduledFor - a.scheduledFor) + if (automationRuns.length > maxPerAutomation) { + automationRuns.sort((a, b) => b.createdAt - a.createdAt || b.scheduledFor - a.scheduledFor) + } // Why: clamp — a negative `slice` end drops from the tail instead of keeping nothing. for (const run of automationRuns.slice(0, Math.max(0, maxPerAutomation))) { kept.add(run.id)