From 9407efd9608a8e4d627fa19c89a612df91b63c3c Mon Sep 17 00:00:00 2001 From: AetherForge Date: Tue, 2 Jun 2026 22:09:09 -0700 Subject: [PATCH] Fix stale alerts for deleted agents on command deck --- server/internal/alerts/evaluator.go | 24 ++++++++++++++++++++ server/internal/api/fleet_handler.go | 8 +++++++ server/web/src/context/WebSocketProvider.tsx | 3 +++ 3 files changed, 35 insertions(+) diff --git a/server/internal/alerts/evaluator.go b/server/internal/alerts/evaluator.go index 979ed88..ff92878 100644 --- a/server/internal/alerts/evaluator.go +++ b/server/internal/alerts/evaluator.go @@ -194,6 +194,30 @@ func (e *Evaluator) ActiveAlerts() []AlertEvent { return out } +// ClearAgent removes all in-memory alert state for a specific agent. +// Call this when an agent is deleted so stale alerts stop appearing on the +// dashboard for machines that no longer exist. +func (e *Evaluator) ClearAgent(agentID string) { + e.mu.Lock() + defer e.mu.Unlock() + // Remove cached alerts for this agent. + filtered := e.activeAlerts[:0] + for _, a := range e.activeAlerts { + if a.AgentID != agentID { + filtered = append(filtered, a) + } + } + e.activeAlerts = filtered + // Remove cooldown + baseline entries so the agent's next appearance + // (e.g. re-registration) starts fresh. + delete(e.baseline, agentID) + for k := range e.lastFired { + if len(k) > len(agentID) && k[len(k)-len(agentID):] == agentID { + delete(e.lastFired, k) + } + } +} + func formatPct(v float64) string { if v < 0 { v = 0 diff --git a/server/internal/api/fleet_handler.go b/server/internal/api/fleet_handler.go index c8b1e11..521de97 100644 --- a/server/internal/api/fleet_handler.go +++ b/server/internal/api/fleet_handler.go @@ -522,6 +522,11 @@ func (f *FleetHandler) DeleteAgent(w http.ResponseWriter, r *http.Request) { http.Error(w, "delete failed: "+err.Error(), http.StatusInternalServerError) return } + // Clear stale in-memory alerts so the machine stops showing up in the + // dashboard alert banner after deletion. + if f.alerts != nil { + f.alerts.ClearAgent(id) + } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]bool{"success": true}) } @@ -543,6 +548,9 @@ func (f *FleetHandler) BulkDeleteAgents(w http.ResponseWriter, r *http.Request) } if err := f.db.DeleteAgent(id); err == nil { deleted++ + if f.alerts != nil { + f.alerts.ClearAgent(id) + } } } w.Header().Set("Content-Type", "application/json") diff --git a/server/web/src/context/WebSocketProvider.tsx b/server/web/src/context/WebSocketProvider.tsx index 31ae25f..4ebb860 100644 --- a/server/web/src/context/WebSocketProvider.tsx +++ b/server/web/src/context/WebSocketProvider.tsx @@ -108,6 +108,9 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) { case 'agent_deleted': { const { agent_id } = msg.payload as { agent_id: string }; setAgents((prev) => prev.filter((a) => a.id !== agent_id)); + // Also purge any cached alerts for this agent so the dashboard + // alert banner stops showing errors for machines that no longer exist. + setFleetAlerts((prev) => prev.filter((a) => a.agent_id !== agent_id)); break; } case 'stats_update': {