diff --git a/internal/alerts/alerts.go b/internal/alerts/alerts.go index ae5a39ebe..19a74ff10 100644 --- a/internal/alerts/alerts.go +++ b/internal/alerts/alerts.go @@ -6159,7 +6159,9 @@ func (m *Manager) preserveAlertState(alertID string, updated *Alert) { func (m *Manager) removeActiveAlertNoLock(alertID string) { delete(m.activeAlerts, alertID) - delete(m.ackState, alertID) + // NOTE: Don't delete ackState here - preserve it so if the same alert + // reappears (e.g., powered-off VM during backup), the acknowledgement + // is restored via preserveAlertState. ackState is cleaned up in Cleanup(). } // GetActiveAlerts returns all active alerts @@ -7945,6 +7947,17 @@ func (m *Manager) Cleanup(maxAge time.Duration) { } } + // Clean up stale ackState entries for alerts that no longer exist + // Keep ackState for 1 hour to handle transient alert clears (e.g., backups) + ackStateTTL := 1 * time.Hour + for id, record := range m.ackState { + if _, alertExists := m.activeAlerts[id]; !alertExists { + if now.Sub(record.time) > ackStateTTL { + delete(m.ackState, id) + } + } + } + // Clean up recent alerts older than suppression window suppressionWindow := time.Duration(m.config.SuppressionWindow) * time.Minute if suppressionWindow == 0 {