Skip to content

Incidents from id-less criteria incident templates can never auto-resolve: resolution paths hard-gate on createdIncidentTemplateId while dedupe normalises it #3390

Description

@p-paul

Summary

An incident created from a criteria whose incident template has no id can never be auto-resolved — not by an incoming status: resolved payload, not by series absence. Both resolution paths hard-gate on createdIncidentTemplateId, which the creation path leaves NULL when the template has no id.

The server accepts id-less templates on write (no validation error, even though CriteriaIncident declares id required), and the dedupe path was explicitly hardened to treat them as legitimate ("legacy/API-authored criteria"). The resolution paths were not given the same treatment. The result is the worst failure shape: incidents are created correctly, deduplicated correctly, carry a correct seriesFingerprint — and silently stay open forever, with no error anywhere.

Verified on 12.0.13 (running deployment) and unchanged in every tag through 12.0.21 (Common/Server/Utils/Monitor/MonitorIncident.ts is byte-identical from 12.0.12 to 12.0.21).

The inconsistency

Creation only sets the template id when present (MonitorIncident.ts):

if (criteriaIncident.id) {
  incident.createdIncidentTemplateId = criteriaIncident.id.toString();
}

The dedupe path knows this and normalises — its own comment names the state:

/*
 * Dedupe match must mirror the create path below (which sets
 * `createdCriteriaId` / `createdIncidentTemplateId` only when the
 * corresponding id is present). A criteria incident template can be
 * missing its `id` (legacy/API-authored criteria), so guard ...
 * Normalise both sides to `undefined` on missing so a created
 * incident (whose template id was left NULL) still matches itself next
 * cycle instead of being recreated as a duplicate.
 */
(incident.createdIncidentTemplateId || undefined) ===
  (criteriaIncident.id?.toString() || undefined)

But both resolution paths gate hard on the same field:

resolveSeriesIncidentsByFingerprint (event-driven, resolvedWhenJSONPath matches):

// Only auto-resolve when the creating criteria opted into it.
if (!createdCriteriaId || !createdIncidentTemplateId) {
  continue;
}

shouldCloseIncident (criteria/absence path) — same gate, in both of its branches:

if (!input.openIncident.createdIncidentTemplateId?.toString()) {
  return false;
}

So a state the write path accepts and the dedupe path explicitly supports is silently unresolvable.

Live reproduction (server 12.0.13)

Incoming Request monitor created through the API (via the Terraform provider, which never sends template ids — reported separately as OneUptime/terraform-provider-oneuptime#16), with:

"incidentGrouping": {
  "groupByJSONPath": "requestBody.groupKey",
  "resolvedWhenJSONPath": "requestBody.status",
  "resolvedWhenValue": "resolved"
}

and autoResolveIncident: true on the template. Reading the monitor back confirms the stored template has no id key (the criteria itself does):

criteria id   : aafe2669-…
incident keys : [autoResolveIncident, description, incidentSeverityId,
                 onCallPolicyIds, showIncidentOnStatusPage, title]   <- no id
  1. POST a firing Alertmanager payload → incident created, seriesFingerprint set, createdCriteriaId set, createdIncidentTemplateId NULL. ✅
  2. Re-POST the same group → correctly deduplicated (the normalised dedupe match works). ✅
  3. POST the same groupKey with "status": "resolved"HTTP 200, incident never resolves.
INC-70  createdCriteriaId: aafe2669-…   createdIncidentTemplateId: NULL   seriesFingerprint: 0eb39d7970a4f69a
INC-71  createdCriteriaId: 1bae1045-…   createdIncidentTemplateId: NULL   seriesFingerprint: edb7f65d5add2188

Suggested fix

Give the resolution gates the same normalisation the dedupe path already has. A runtime detail makes this a genuinely small change: the auto-resolve dictionary is built with

autoResolveCriteriaInstanceIdIncidentIdsDictionary[criteriaId].push(incidentTemplate.id);

so for an id-less template the array already contains undefined at runtime. Relaxing the gate to require only createdCriteriaId, and comparing with (x || undefined) on both sides, makes [undefined].includes(undefined) match and resolution work — for existing incidents and monitors too, which a provider-side fix alone cannot repair.

(Independently, the provider should send stable template ids — that's #16 on the provider repo — and/or the API could enforce the CriteriaIncidentSchema requirement on write so the state can't arise silently. But the server fix is the only one that heals monitors already in this state.)

Impact

Anyone driving OneUptime through the API or Terraform who uses incidentGrouping + autoResolveIncident gets working creation and dedup and permanently broken auto-resolve, with nothing in any log to say why. In our deployment this is the Alertmanager receiver, where auto-resolution on recovery was the point of the feature.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions