Files
sim/apps
Waleed 009f5fea7f fix(logs): record how long a cancelled run had been going (#6686)
* fix(logs): record how long a cancelled run had been going

A cancelled run got an end timestamp and no duration. Every other
terminal transition writes both — the completion path sets them together,
and a paused run already records its elapsed time — but cancellation
writes the log row directly rather than through completion, so it had no
in-memory duration to store and simply omitted the column.

That is not cosmetic. `GET /api/v2/logs` filters on `minDurationMs` and
`maxDurationMs`, and a null column drops the row out of every such query,
so cancellations are invisible to exactly the searches someone runs when
investigating cancellations. The published contract also says the end
timestamp is null only while a run is active, which a cancelled run
is not.

Both cancellation writes now derive the duration in the same statement
from the row's own `started_at`, through one shared expression so the two
cannot drift apart the way they did from the completion path. The end
instant is computed once and reused, so the stamped end and the derived
duration describe the same moment rather than two clock reads.

The instant is bound as an explicit `timestamp` rather than a `Date`:
`started_at` is `timestamp without time zone` holding a UTC wall clock,
and a driver-bound date would infer `timestamptz` and make the interval
depend on the session zone. The floor of one millisecond matches the
completion path, so a run cancelled inside its first millisecond still
records that it ran.

* fix(logs): saturate the cancelled-run duration at the column ceiling

The column is `integer`, so an untimed run cancelled after roughly
twenty-five days overflowed the cast. That cost more than the duration it
was recording: the direct write is caught and logged, so the row would
have stayed `running` with no end timestamp at all, and the
workflow-group write would have failed its transaction and taken the
whole cancellation with it.

Saturating keeps the terminal write. A duration wrong in its last digits
is a smaller lie than a run that never ended.

* fix(logs): record the duration on the other two cancellation writes

Review found the first pass had only covered two of four terminal
cancellation writes. The two it missed spell the timestamp `endedAt: now`
rather than `endedAt: new Date()`, so the search that found the first pair
could never have found them — and one of them is the common case: a
workflow-group run with a live cell sidecar takes that branch, and the
direct cancel skips its own log update whenever group cancellation
handled the run, so it was the only writer for those cancellations.

The other is the paused-cancellation write, which the first pass reported
as already correct on the strength of that same search. A paused run
records its duration when it pauses; cancelling it did not.

All four now derive the duration the same way, and the sweep for the
remaining ones went over every `status: 'cancelled'` write rather than one
spelling of the timestamp beside it.

* fix(logs): let a recorded duration outlive a later cancellation

A paused run measures its own active duration at the pause checkpoint.
The previous commit then had cancellation overwrite that with wall clock
from the start, which quietly redefines the column for those runs to
include the time the run spent waiting rather than working — filling a
gap by discarding an answer someone else had already computed.

The duration now coalesces onto whatever the row already carries, so a
cancellation only supplies the value when nothing else did. Every other
cancellation path leaves the column null, so the change is inert there.

* fix(logs): only a paused run keeps the duration it recorded

Preserving any duration already on the row was too broad. Resuming flips
the log back to running and leaves the pause checkpoint value behind, so
a resumed run carries a stale reading while it is accruing time again;
cancelling it would have frozen that pre-resume figure and disagreed with
the resume completion path, which measures wall clock.

What separates the two is the row's status rather than whether the column
is populated. A paused run is not accruing, so its recorded active
duration stands. A running one recomputes.
2026-08-13 17:55:37 -07:00
..