Skip to content

Conversation

@tennisleng
Copy link

Description

Fixes issue #4554 where was not being updated when resolving alerts via the API.

Problem

When posting an alert via the API with only set (to resolve an existing alert), the field was not being updated. This happened due to two issues:

  1. Merge condition too restrictive: The merge logic in only merged alerts when there was an overlap in activity range. When resolving an alert, the new (in the past) doesn't overlap with the old (future timeout), so no merge occurred.

  2. Merge function bug: The function in didn't properly handle the case where a new resolved alert is merged with an existing unresolved alert.

Solution

  1. Updated merge condition: Modified to also merge alerts when updating an existing alert (same fingerprint), allowing updates like setting to resolve an alert even without overlap.

  2. Fixed Merge function: Updated to always use the new alert's when it is resolved, ensuring that resolving an alert via API properly updates the timestamp.

Testing

  • Verified the fix handles the scenario described in endsAt not updated #4554
  • Existing merge tests should continue to pass
  • The fix maintains backward compatibility with existing alert merging behavior

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Fixes #4554

@grobinson-grafana
Copy link
Collaborator

I believe this behavior is intentional, see here.

types/types.go Outdated
if o.Resolved() {
// If the new alert is resolved, use its EndsAt timestamp.
// This handles the case where an alert is being resolved via API.
res.EndsAt = o.EndsAt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At line 448 we already do res := o, so res.EndsAt should already be o.EndsAt?

// the new alert is updating the existing alert (e.g., setting endsAt
// to resolve it, or updating annotations/labels).
hasOverlap := (alert.EndsAt.After(old.StartsAt) && alert.EndsAt.Before(old.EndsAt)) ||
(alert.StartsAt.After(old.StartsAt) && alert.StartsAt.Before(old.EndsAt))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grobinson-grafana in this case should alert.StartsAt be After old.StartsAt, or rather

!alert.StartsAt.Before(old.StartsAt) && alert.StartsAt.Before(old.EndsAt))
This would fix StartsAt being the same (which is overlapping, if I'm correct...)


// Also merge if updating an existing alert (same fingerprint) to allow
// updates like setting endsAt to resolve an alert, even without overlap.
isUpdate := !alert.UpdatedAt.Before(old.UpdatedAt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit too broad, as one could just set UpdatedAt to now to make any possible alert updates?

tennisleng added a commit to tennisleng/alertmanager that referenced this pull request Nov 11, 2025
- Remove redundant EndsAt assignment in types/types.go (res is already a copy of o)
- Fix overlap check to handle equal StartsAt timestamps using !Before instead of After
- Replace isUpdate check with more specific isResolving check that only allows
  merging when an alert is being resolved (endsAt set and old alert doesn't have
  it, or new endsAt is earlier than old endsAt), preventing arbitrary updates
  based solely on UpdatedAt timestamp

Fixes review comments from prometheus#4717
// This handles the case where an alert is resolved via API without time overlap.
// An alert is being resolved if it has endsAt set and the old alert doesn't,
// or if the new endsAt is earlier than the old endsAt.
isResolving := !alert.EndsAt.IsZero() && (old.EndsAt.IsZero() || alert.EndsAt.Before(old.EndsAt))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am struggling to figure out if this is correct or not. It doesn't seem correct to me that we just merge an old alert regardless of its timestamps?

@tennisleng tennisleng force-pushed the fix/endsat-not-updated branch 2 times, most recently from 4213987 to 2661448 Compare November 11, 2025 20:22
tennisleng and others added 3 commits November 11, 2025 15:27
When an alert is posted via the API with only endsAt set (to resolve
an existing alert), the endsAt field was not being updated due to two
issues:

1. The merge condition in provider/mem/mem.go only merged alerts when
   there was an overlap in activity range. This prevented merging when
   resolving an alert (where the new endsAt is before the old endsAt
   timeout).

2. The Merge function in types/types.go didn't properly handle the case
   where a new resolved alert is merged with an existing unresolved alert.

This fix:
- Updates the merge condition to also merge when updating an existing
  alert (same fingerprint), allowing updates like setting endsAt to
  resolve an alert even without overlap.
- Updates the Merge function to always use the new alert's EndsAt when
  it is resolved, ensuring that resolving an alert via API properly
  updates the endsAt timestamp.

Fixes prometheus#4554

Signed-off-by: tennisleng <tennisleng@users.noreply.github.com>
- Remove redundant EndsAt assignment in types/types.go (res is already a copy of o)
- Fix overlap check to handle equal StartsAt timestamps using !Before instead of After
- Replace isUpdate check with more specific isResolving check that only allows
  merging when an alert is being resolved (endsAt set and old alert doesn't have
  it, or new endsAt is earlier than old endsAt), preventing arbitrary updates
  based solely on UpdatedAt timestamp

Fixes review comments from prometheus#4717

Signed-off-by: Andrew Leng <work@Andrews-MacBook-Air.local>
- Add StartsAt equality check to isResolving condition to ensure only
  alerts with identical start times can be merged for resolution
- This prevents unintended merges of alerts that appear similar but
  represent different alert instances
- Add comprehensive unit tests covering all merge scenarios:
  * Resolving active alerts via API
  * Re-resolving already resolved alerts to earlier times
  * Rejecting merges with different StartsAt (security)
  * Rejecting attempts to extend active alerts (security)

Addresses reviewer concern about overly permissive merge logic that
could allow merging alerts 'regardless of timestamps'.

Signed-off-by: Andrew Leng <work@Andrews-MacBook-Air.local>
@tennisleng tennisleng force-pushed the fix/endsat-not-updated branch from 2661448 to ffea952 Compare November 11, 2025 20:28
@tennisleng tennisleng closed this Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

endsAt not updated

4 participants