Alert Status Management#
Alert status management in Keep covers how individual and bulk status changes flow from the UI through the API to persistence, workflow triggering, and incident resolution. Key concerns are: preventing duplicate submissions, deduplicating incident resolution checks, and efficiently evaluating workflow triggers across large alert batches.
Alert Status Change Flow#
Alert status updates are processed as enrichments β arbitrary key/value metadata attached to an alert fingerprint. A status change sets status (and optionally dismissed/dismissUntil) in the enrichment payload.
Keep supports five statuses: firing, resolved, acknowledged, suppressed, and pending .
Single-alert path:
The AlertChangeStatusModal posts to POST /alerts/enrich?dispose_on_new_alert=<bool> with a fingerprint and enrichments body . The backend handler enrich_alert delegates to _enrich_alert.
Bulk path:
batch_enrich_alerts handles POST /alerts/batch_enrich. It accepts either an explicit fingerprints list or a CEL expression (never both simultaneously) . When cel is given, matching alerts are resolved first via query_last_alerts and their fingerprints extracted . After fingerprints are resolved either way, the function calls enrichment_bl.batch_enrich(...) and then:
- Indexes enriched alerts to Elasticsearch
- Pushes a Pusher
poll-alertsevent to connected clients - Triggers workflow evaluation via
WorkflowManager.insert_events(...)if applicable - Checks whether any linked incidents should auto-resolve
UI Safety β Duplicate Submission Prevention#
Without protection, double-clicking "Change Status" fires duplicate API requests and shows duplicate success toasts. PR #6622 addresses this with an isSubmitting state flag in AlertChangeStatusModal.
- Both
handleChangeStatus(single alert) andhandleChangeStatusBatch(bulk) setisSubmitting(true)before making API calls and reset it in afinallyblock. - The Cancel and primary action buttons are disabled while
isSubmittingis true, preventing re-entry. - Batch mode includes an early-return guard: if no status is selected, an error toast is shown and the function exits immediately .
Dispose on new alert toggle:
The modal also exposes a "Dispose on new alert" switch , defaulting to true. When enabled (dispose_on_new_alert=true), the status enrichment is cleared automatically when a new alert with the same fingerprint arrives β preventing a stale manual status from persisting on a re-firing alert.
Backend β Incident Resolution Deduplication#
When a bulk status change resolves alerts, Keep must also check whether their parent incidents should auto-resolve. The logic lives at the end of batch_enrich_alerts.
Current (pre-fix) logic:
for alert in alerts:
for incident in alert._incidents:
if incident.resolve_on == ResolveOn.ALL and is_all_alerts_resolved(...):
incident.status = RESOLVED
session.add(incident)
session.commit() # β called inside nested loop
This causes the same incident to be processed and committed multiple times when multiple enriched alerts belong to the same incident .
Post-fix approach (PR #6622):
Build a unique_incidents = {incident.id: incident ...} dict across all alerts, then iterate unique_incidents.values() once β guaranteeing each incident is evaluated at most once β followed by a single session.commit() outside the loop.
The resolution gate uses two conditions: incident.resolve_on == ResolveOn.ALL.value (incident is configured to resolve only when all alerts resolve) and is_all_alerts_resolved(incident, session) returning True . Incident data is hydrated beforehand via enrich_alerts_with_incidents(tenant_id, alerts) .
Backend β Workflow Caching Across Batch Operations#
After enrichment, if workflows should run, batch_enrich_alerts calls WorkflowManager.insert_events(tenant_id, enriched_alerts_dto). The insert_events method evaluates which workflows should fire for each alert in the batch.
Pre-fix behavior in insert_events: for every event in the batch, all tenant workflows are re-fetched from the database and re-parsed. This creates O(events Γ workflows) complexity β for a bulk change of 100 alerts across 20 workflows, that is 2,000 parse operations.
Post-fix approach (PR #6622):
- Fetch all workflow models once before the event loop.
- Parse them into a
parsed_workflowslist (one_get_workflow_from_store()call per workflow). - Reuse
parsed_workflowsfor every event's trigger evaluation β reducing complexity to O(workflows + events). - For each workflow that does match and gets queued, a fresh workflow instance is created via another
_get_workflow_from_store()call. This ensures that any state mutations from trigger evaluation don't bleed into the actual execution context .
Matched workflows are enqueued via scheduler.workflows_to_run.append(...) under a threading lock . Trigger evaluation uses CEL (Common Expression Language) with backward-compatibility support for legacy filter formats.
Key Files & Entry Points#
| Component | Location | Purpose |
|---|---|---|
| Status change modal | alert-change-status-modal.tsx | UI: single and bulk status change, duplicate submission guard |
| Alert API routes | keep/api/routes/alerts.py | enrich_alert (line 999), batch_enrich_alerts (line 793) |
| Workflow manager | keep/workflowmanager/workflowmanager.py | insert_events (line 287) β workflow trigger evaluation |
| DB helpers | keep/api/core/db.py | enrich_alerts_with_incidents, is_all_alerts_resolved |
Related PR: #6622 β Fix slow bulk alert status changes and prevent duplicate submissions (open as of 2026-07-08) β the definitive reference for all three optimizations described above.