Skip to content

fix(alerts): v2 rules report last_evaluated_at for healthy rules - #2326

Merged
Makisuo merged 1 commit into
mainfrom
fix/alert-rules-last-evaluated-at
Nov 7, 2026
Merged

Makisuo merged 1 commit into
mainfrom
fix/alert-rules-last-evaluated-at

Conversation

@Makisuo

@Makisuo Makisuo commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

The v2 rules API returned last_evaluated_at: null for every healthy rule. AlertRulesService only took the time from a rule-state row that carried an evaluation error.

What changed

  • last_evaluated_at is now the newest evaluation across all of a rule's state rows. That includes the __total__ heartbeat row that anomaly rules write every tick.
  • The evaluation error and its kind still come from the newest row that has one. A successful tick clears its row's error, so a row with an error means that group's latest evaluation failed.
  • The rule document returned after an update (the v2 PATCH response) now loads the same state. Before, it never had any. List, GET and PATCH now agree.

Readers

  • Dashboard: unaffected. It reads the synced state rows directly and already takes the newest time.
  • iOS: no screen reads the field. It only appears in fixtures and tests.

Verification

  • AlertRulesService.test.ts, 5 new tests:

    • a healthy rule reports its time;
    • an erroring rule keeps its error, kind and time;
    • with several group rows, the newest time wins;
    • an anomaly rule with only a __total__ row reports that time;
    • a rule with no state rows stays null.

    3 of these fail without the fix. The file passes 7/7.

  • apps/api v2 alerts.http.test.ts: a new wire test covers create, then list, GET and PATCH after an evaluation. 37/37 pass.

  • Typecheck is clean in backend and api. Both oxlint configs are clean.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Summary by CodeRabbit

  • Bug Fixes
    • Alert rules now show the most recent evaluation time and relevant error status across grouped evaluations. A successful evaluation no longer hides an error from another group.
    • Rules without evaluation history continue to show empty evaluation details, and anomaly alerts reflect their heartbeat evaluation state.
    • Updating an alert rule’s name preserves its last evaluation time.

…rors

listRules only read state rows that carried an error, so healthy rules always
reported a null last evaluation. The time is now the newest across all of a
rule's group rows (anomaly __total__ heartbeats included); the error still
comes from the newest row that has one. The document returned by a rule update
(v2 PATCH) gets the same state, so list, retrieve and update agree.
@maple-review-bot

maple-review-bot Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 10/10 · safe to merge. Aggregation, both callers and the GET route that reads it, plus the new tests, all check out; nothing to fix.
no findings · tests covered · risk medium · 1/1 new units observable

Makes last_evaluated_at the newest evaluation across a rule's state rows instead of only error rows, and gives the PATCH response the same state list and GET already had. A contained read-path fix with tests on both the service and the wire.

  • evaluationStatesByRule takes evaluatedAt from the newest state row and the error from the newest row carrying one
  • loadEvaluationStates replaces the inline list query and filters by ruleId for the edit path
  • upsertRuleRow now loads evaluation state, so the v2 PATCH response reports last_evaluated_at

72a6dd9 · What was checked, coverage and telemetry details are on the Maple / review check. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@coderabbitai

coderabbitai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The alert rule service now aggregates evaluation timestamps and error details across state rows. List and upsert operations include this state in rule documents. Service and HTTP tests cover healthy, errored, grouped, anomaly, and missing evaluation state.

Changes: Alert Evaluation State

Layer / File(s) Summary
Aggregate evaluation state
packages/backend/src/services/alerts/AlertRulesService.ts
The service calculates the greatest non-null evaluation timestamp per rule. It selects error details from the newest row with a non-null error, treating null timestamps as zero.
Expose state in rule documents
packages/backend/src/services/alerts/AlertRulesService.ts, packages/backend/src/services/alerts/AlertRulesService.test.ts, apps/api/src/routes/v2/alerts.http.test.ts
List and upsert operations pass aggregated state to rule documents. Tests cover healthy and errored evaluations, multiple groups, anomaly rules, missing state, and HTTP responses after create, list, retrieve, and PATCH.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Possibly related PRs

Suggested reviewers: jeremyfunk

Merge Risk: Minimal · up to 72a6d

No actionable merge-blocking risk is established for this change; it is ready for normal merge checks.

Pre-merge checks: Passed 5

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the fix for healthy rules in the v2 alerts API.
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 3 files.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 517540c4-d7e2-496b-bd0e-c02291416078
Commits

Reviewing files that changed from the base of the PR and between 2cc3cac and 72a6dd9.

Files selected for processing: 3

  • apps/api/src/routes/v2/alerts.http.test.ts
  • packages/backend/src/services/alerts/AlertRulesService.test.ts
  • packages/backend/src/services/alerts/AlertRulesService.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@Makisuo Makisuo added the preview:web Deploy a Workers-only PR preview (no ingest/Electric); see docs/pr-previews.md label Nov 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

🍁 Maple PR preview

Note

Preview resources were removed when the preview label came off.

Final commit 72a6dd9 · View workflow run

@Makisuo

Makisuo commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator Author

Preview test (preview:web, app-pr-2326)

A separate agent signed in to the preview and called the v2 API as the shared reviewer. Times are UTC on 2026-11-07.

Check Result
New enabled error_rate rule, before its first evaluation last_evaluated_at: null from the create response, GET and the list
Same rule after its first evaluation (11:43:40) the list, GET and two PATCH responses all return 2026-11-07T11:43:40.930Z, with last_evaluation_error: null
Scoped anomaly rule null at creation, then 2026-11-07T11:44:41.599Z after its first tick, with no error
Disabled rule keeps its last evaluation time
Cleanup both rules and the test webhook destination deleted

Not covered on the preview:

  • A rule with an evaluation error: none of the test rules errored.
  • The org's default anomaly rule: the Preview org doesn't have one.
  • The value moving forward on a later evaluation: each rule was evaluated once and then not again, and last_scheduled_at stayed frozen. The preview scheduler was not re-running rules, which is separate from this change. The newest-row selection is covered by the unit tests.

Traces (deployment.environment = pr-2326)

Span n Duration Postgres queries
AlertRulesService.listRules 7 13 to 19 ms 2: SELECT alert_rules, then SELECT alert_rule_states
AlertRulesService.loadEvaluationStates 9 4 to 7 ms 1
GET /v2/alerts/rules, whole request 6 13 to 120 ms

The slow requests spend about 100 ms in OrganizationRegionService.ensureServedHere, a cold org lookup through the cache, before any rule code runs.

PATCH /v2/alerts/rules/:id (trace 81ae2a16117bfde337008d01bfe08ccd) takes 219 ms in total:

  • 133 ms is the cold org region lookup.
  • listRules takes 17 ms. That read is unchanged by this PR.
  • upsertRuleRow takes 57 ms. Its new per-rule state read, scoped to the rule's id, adds 7 ms.

There were no errors, retries or N+1 reads. The state read is one query per request, the same query the list already ran before this change.

@Makisuo Makisuo removed the preview:web Deploy a Workers-only PR preview (no ingest/Electric); see docs/pr-previews.md label Nov 7, 2026
@Makisuo
Makisuo merged commit c22a7b3 into main Nov 7, 2026
64 of 66 checks passed
@Makisuo
Makisuo deleted the fix/alert-rules-last-evaluated-at branch November 7, 2026 16:49

This branch was previously deployed

1 inactive deployment
pr-preview — 72a6dd9a Deployed Nov 7, 2026 by Makisuo via deploy-pr-preview #10147
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant