Repair duration display for bad stopped timestamps (#37121)

Workflow run, job, task, and step durations could show **negative**
values (e.g. `-50s`) when `Stopped` was missing, zero (epoch), or
**before** `Started` (clock skew, races, reruns). The UI used
`calculateDuration` with no validation.

This change:

- Uses each row`s **Updated** timestamp as a **fallback end time** when
`Stopped` is invalid but the status is terminal, so duration
approximates elapsed time instead of `0s` or a negative.
- Keeps **`ActionRun.Duration()`** clamped to **≥ 0** when
`PreviousDuration` plus the current segment would still be negative
(legacy bad data).

Fixes #34582.

Co-authored-by: Composer <composer@cursor.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Nicolas
2026-04-07 04:11:52 +02:00
committed by GitHub
parent ff777cd2ad
commit fc23bd7b3a
7 changed files with 78 additions and 10 deletions
+43 -4
View File
@@ -45,9 +45,10 @@ func Test_calculateDuration(t *testing.T) {
return timeutil.TimeStamp(1000).AsTime().Sub(t)
}
type args struct {
started timeutil.TimeStamp
stopped timeutil.TimeStamp
status Status
started timeutil.TimeStamp
stopped timeutil.TimeStamp
status Status
fallbackEnd timeutil.TimeStamp
}
tests := []struct {
name string
@@ -81,10 +82,48 @@ func Test_calculateDuration(t *testing.T) {
},
want: 100 * time.Second,
},
{
name: "done_stopped_zero_no_fallback",
args: args{
started: 500,
stopped: 0,
status: StatusSuccess,
},
want: 0,
},
{
name: "done_stopped_zero_uses_fallback",
args: args{
started: 500,
stopped: 0,
status: StatusSuccess,
fallbackEnd: 600,
},
want: 100 * time.Second,
},
{
name: "done_stopped_before_started_no_fallback",
args: args{
started: 600,
stopped: 550,
status: StatusSuccess,
},
want: 0,
},
{
name: "done_stopped_before_started_uses_fallback",
args: args{
started: 600,
stopped: 550,
status: StatusSuccess,
fallbackEnd: 650,
},
want: 50 * time.Second,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, calculateDuration(tt.args.started, tt.args.stopped, tt.args.status), "calculateDuration(%v, %v, %v)", tt.args.started, tt.args.stopped, tt.args.status)
assert.Equalf(t, tt.want, calculateDuration(tt.args.started, tt.args.stopped, tt.args.status, tt.args.fallbackEnd), "calculateDuration(%v, %v, %v, %v)", tt.args.started, tt.args.stopped, tt.args.status, tt.args.fallbackEnd)
})
}
}