From e1dbce02c22bcaa3d7d447ee54db69373bc1cf7b Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:16:50 +0200 Subject: [PATCH] fix: compute wall-clock duration for flow job groups in CLI (#8826) The total duration of a for-loop/branchall group was computed as the naive sum of all iteration durations. This is wrong for parallel execution and doesn't account for orchestration overhead. Instead, compute actual wall-clock time as max(completed_at) - min(started_at). Co-authored-by: Claude Opus 4.6 (1M context) --- cli/src/commands/job/job.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/cli/src/commands/job/job.ts b/cli/src/commands/job/job.ts index f542355fb7..454a600a19 100644 --- a/cli/src/commands/job/job.ts +++ b/cli/src/commands/job/job.ts @@ -155,21 +155,36 @@ function formatFlowSteps( // For-loop modules: show parent line + iteration sub-lines const flowJobs = mod.flow_jobs as string[] | undefined; if (flowJobs && flowJobs.length > 0) { - // Total duration for the for-loop - const totalMs = flowJobsDuration?.duration_ms - ? (flowJobsDuration.duration_ms as number[]).reduce((a: number, b: number) => a + b, 0) - : undefined; + // Compute actual wall-clock duration for the group from started_at + duration_ms + const startedAtArr = (flowJobsDuration?.started_at ?? []) as (string | null)[]; + const durationMsArr = (flowJobsDuration?.duration_ms ?? []) as (number | null)[]; + let totalMs: number | undefined; + { + let minStart = Infinity; + let maxEnd = -Infinity; + for (let i = 0; i < startedAtArr.length; i++) { + const sa = startedAtArr[i]; + const dm = durationMsArr[i]; + if (sa != null && dm != null) { + const startMs = new Date(sa).getTime(); + minStart = Math.min(minStart, startMs); + maxEnd = Math.max(maxEnd, startMs + dm); + } + } + if (minStart < Infinity && maxEnd > -Infinity) { + totalMs = maxEnd - minStart; + } + } const durationStr = totalMs != null ? colors.dim(formatDuration(totalMs)) : ""; console.log(` ${icon} ${label} ${durationStr}`); const flowJobsSuccess = (mod.flow_jobs_success ?? []) as boolean[]; - const durationMs = (flowJobsDuration?.duration_ms ?? []) as number[]; for (let iter = 0; iter < flowJobs.length; iter++) { const iterSuccess = flowJobsSuccess[iter]; const iterIcon = iterSuccess === true ? colors.green("✓") : iterSuccess === false ? colors.red("✗") : colors.dim("·"); - const iterDur = durationMs[iter] != null ? colors.dim(formatDuration(durationMs[iter])) : ""; + const iterDur = durationMsArr[iter] != null ? colors.dim(formatDuration(durationMsArr[iter]!)) : ""; const iterJobId = colors.dim(flowJobs[iter]); console.log(` ${iterIcon} iteration ${iter} ${iterJobId} ${iterDur}`); }