Skip to content

fix(alerts): tighten ai detector unavailable message, metric and queue routing #105367

Description

@andrewm4894

Bug Description

Follow-ups to #105304, which moved AI detector (llm) alert evaluation onto MAX_AI_TASK_QUEUE. The review on that PR raised these points. None of them blocked the merge, and all are small.

1. The "provider unreachable" message covers failures that are not the provider

_failed_evaluation_error in posthog/temporal/alerts/activities.py maps every LLMDetectorUnavailableError to LLM_DETECTOR_UNAVAILABLE_MESSAGE, which says the detector "could not reach its model provider". That exception also covers failures that happen before or outside a provider request:

  • The rollout flag lookup is unavailable.
  • The worker's model-call slots (MAX_CONCURRENT_MODEL_CALLS) are full.
  • The model output cannot be read.

In those cases the check history, the API, the error email and the in-app notification all tell the owner the wrong cause.

Fix: make the message describe the outcome, not the cause. For example: "The AI detector could not produce a verdict, so this check did not run. The alert is still on and the next check tries again." Another option is a provider-specific subtype with its own code, keeping the generic message for the base class.

The same PR's email and notification copy say "The alert and insight settings are correct". Nothing verifies that. Wording like "There is nothing to change in the alert" is accurate without the claim.

2. The AI detector check counter counts attempts, not checks

record_ai_detector_check_outcome("unavailable") runs on every failed activity attempt, before the exception goes back to Temporal's retry policy. So one scheduled check can add five unavailable outcomes. A transient failure followed by a success adds both unavailable and evaluated. Ratios built on posthog_insight_alerts_ai_detector_checks_total overstate failures.

Fix: record unavailable once, in the retry-exhausted path (record_failed_evaluation), and keep evaluated and misconfigured where they are. If per-attempt counts are useful, put them in a separate metric named for attempts.

3. The queue choice can go stale between prepare and evaluate

CheckAlertWorkflow picks the evaluate task queue from prepare_result.uses_llm_detector, but evaluate_alert reloads the alert. If the alert is switched from a statistical detector to the AI detector between the two activities, the evaluation runs on ANALYTICS_PLATFORM_TASK_QUEUE and fails without model credentials until its retries run out. The fingerprint check stops the stale failure from being persisted, but the check still fails and stays due until the next scheduler run. The reverse edit runs a statistical evaluation on the AI worker, which is harmless.

Fix: inside evaluate_alert, compare the reloaded detector type with the queue the activity is running on. On a mismatch, return a skip result so the next scheduled check routes correctly.

How to reproduce

  1. Items 1 and 2: make the judge raise LLMDetectorUnavailableError from a non-provider path, such as a saturated slot pool or an unparseable model response. Then read the recorded check error and the counter values.
  2. Item 3: edit an alert's detector to llm between prepare_alert and evaluate_alert in a workflow test.

Acceptance criteria

  • The owner-facing unavailable message never names a cause that was not checked.
  • One scheduled check that exhausts its retries adds exactly one unavailable to the counter, and a retry that succeeds adds only evaluated.
  • A workflow or activity test covers a detector-type change between prepare and evaluate, and the check does not run on the queue that lacks credentials.

Additional context

Rollout ordering was also raised. The sender and the receiver registration shipped in the same deploy, so for a few minutes AI checks could reach MAX_AI_TASK_QUEUE before the worker there registered evaluate_alert. At the time, every AI check was already failing and the detector was behind a flag, so no staged rollout was needed. Keep this in mind for the next queue move on a path with real traffic.

Activity

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions