From 988448664ae2034ac760f8817d04ab8e1cfc9b84 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 19 Apr 2026 20:21:39 -0400 Subject: [PATCH] fix(runs): stamp completed_at on cancel/terminal SetState transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CancelRun goes through Runner.Transition → Runs.SetState, which was a bare UPDATE state=? with no completed_at write. The host-page runDuration helper treats nil CompletedAt as "still running", so a cancelled run kept ticking forever. MarkCompleted / MarkFailed / MarkDispatchFailed already stamp completed_at; SetState now does the same for any terminal target state, using COALESCE so we never clobber an already-set timestamp. Co-Authored-By: Claude Opus 4.7 --- internal/store/runs.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/store/runs.go b/internal/store/runs.go index 9f40b0d..77c7ba3 100644 --- a/internal/store/runs.go +++ b/internal/store/runs.go @@ -45,6 +45,13 @@ func (r *Runs) CreateWithProfile(ctx context.Context, hostID int64, tokenHash st } func (r *Runs) SetState(ctx context.Context, runID int64, state model.RunState) error { + if state.IsTerminal() { + _, err := r.DB.ExecContext(ctx, ` + UPDATE runs SET state = ?, completed_at = COALESCE(completed_at, ?) + WHERE id = ? + `, string(state), time.Now().UTC(), runID) + return err + } _, err := r.DB.ExecContext(ctx, `UPDATE runs SET state = ? WHERE id = ?`, string(state), runID) return err }