diff --git a/scripts/ci-run-timings.mjs b/scripts/ci-run-timings.mjs index 10b9af30455..eb701665150 100644 --- a/scripts/ci-run-timings.mjs +++ b/scripts/ci-run-timings.mjs @@ -127,6 +127,9 @@ function collectRunTimingContext(run) { */ export function summarizeRunTimings(run, limit = 15) { const { created, jobs, updated } = collectRunTimingContext(run); + if (jobs.length === 0) { + throw new Error("CI run timing summary requires at least one job"); + } const byDuration = [...jobs] .filter((job) => job.durationSeconds !== null) .toSorted((left, right) => right.durationSeconds - left.durationSeconds) @@ -312,6 +315,9 @@ function loadRun(runId) { function summarizeJobs(run) { const { created, jobs, updated } = collectRunTimingContext(run); + if (jobs.length === 0) { + throw new Error("CI run timing summary requires at least one job"); + } const completedJobs = jobs.filter((job) => job.started !== null && job.completed !== null); const successfulDurations = jobs .filter((job) => job.status === "completed" && job.conclusion === "success") diff --git a/test/scripts/ci-run-timings.test.ts b/test/scripts/ci-run-timings.test.ts index deca4f99f2f..5d4942f801f 100644 --- a/test/scripts/ci-run-timings.test.ts +++ b/test/scripts/ci-run-timings.test.ts @@ -54,6 +54,18 @@ describe("scripts/ci-run-timings.mjs", () => { ]); }); + it("rejects empty CI job payloads instead of printing empty timing evidence", () => { + expect(() => + summarizeRunTimings({ + conclusion: "success", + createdAt: "2026-04-22T10:00:00Z", + jobs: [], + status: "completed", + updatedAt: "2026-04-22T10:01:30Z", + }), + ).toThrow("CI run timing summary requires at least one job"); + }); + it("selects the push CI run for the current main SHA", () => { expect( selectLatestMainPushCiRun(