diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index d7d81b82e..c8484321c 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -2368,6 +2368,14 @@ func (m *Monitor) recordTaskResult(instanceType InstanceType, instance string, p status.LastSuccess = now status.ConsecutiveFailures = 0 status.FirstFailureAt = time.Time{} + // A recorded error means "current outstanding failure", not "last error + // ever". Clearing it on success keeps downstream consumers honest: the + // connections aggregator surfaces LastError as a live error banner and + // derives Unauthorized state from it, so a stale entry would show a + // red error on a healthy connection forever (#1493). + status.LastErrorAt = time.Time{} + status.LastErrorMessage = "" + status.LastErrorCategory = "" m.mu.Unlock() if breaker != nil { breaker.recordSuccess() diff --git a/internal/monitoring/monitor_polling_test.go b/internal/monitoring/monitor_polling_test.go index b6fbc7e65..12ae7c1c4 100644 --- a/internal/monitoring/monitor_polling_test.go +++ b/internal/monitoring/monitor_polling_test.go @@ -1308,6 +1308,18 @@ func TestRecordTaskResult_SuccessResetsFailures(t *testing.T) { if !m.pollStatusMap[key].FirstFailureAt.IsZero() { t.Error("expected FirstFailureAt to be reset to zero") } + // The recorded error must clear too: it means "current outstanding + // failure", and the connections UI renders it as a live error banner. + // A stale entry would show a red error on a healthy connection (#1493). + if m.pollStatusMap[key].LastErrorMessage != "" { + t.Errorf("expected LastErrorMessage to be cleared, got %q", m.pollStatusMap[key].LastErrorMessage) + } + if !m.pollStatusMap[key].LastErrorAt.IsZero() { + t.Error("expected LastErrorAt to be reset to zero") + } + if m.pollStatusMap[key].LastErrorCategory != "" { + t.Errorf("expected LastErrorCategory to be cleared, got %q", m.pollStatusMap[key].LastErrorCategory) + } } func TestRecordTaskResult_NilMaps(t *testing.T) {