launchdarkly-alert-investigation
Investigates a firing or recently fired observability alert: finds the firing and replays the value it evaluated, decides whether the alert is chronic noise or a new problem, reuses any automatic inve
Files8
View on GitHubSKILL.mdskills/launchdarkly-alert-investigation/SKILL.md
GitHubAlert investigation
You are explaining why an alert fired, whether it matters, and what to do. Half of the alerts you are asked about will turn out to be chronic or misconfigured; "this alert is noise, here is why, here is the retune" is a correct and useful answer. Do not invent a root cause for an alert that has fired every day for a week.
Prerequisites
This skill requires the remotely hosted LaunchDarkly MCP server. Tools it uses:
get-alerts: what is firing, and one alert's full configurationget-alert-firing-history: when it fired, per group, withreplayarguments for each firingget-investigation: the automatic investigation that ran for a firing, if anysearch-aggregations: replay the alert's value, compare against a baseline, break it downget-changes: deploys, flag changes and alert state changes around the onsetget-trace,search-traces,search-logs,search-log-patterns,search-error-groups,get-error-group,search-sessions: evidencesearch-service-map,get-keys,get-incidents
The investigation itself follows the "first five minutes" in launchdarkly-investigate. This skill covers the alert-specific steps around it.
Workflow
1. Find the alert and the firing
- If you have an alert ID (from the user, a notification link or the conversation), call
get-alertswithalertIdfor its query, product type, function, grouping, threshold and window. - If the user asks what is firing, call
get-alertswithstate: "Alert". One call lists every alert whose most urgent group is in Alert now. Usesearchto match an alert by name. - Then call
get-alert-firing-historywith thealertIdandlookbackDays: 7. It returns:evaluation: the query, function, grouping and thresholds it uses nowfiring_countandlast_fired_atfirings: newest first, each with itsgroup, state change,investigation_conversation_idandreplaycurrent_state: the latest state per group, which tells an alert firing now from one that has resolvedsummary: the episode view, read from up to 500 firings (firings_counted,complete). See step 3.
Name the group that fired (for example task.type=5), not just the alert. current_state leaves out groups that have been Normal and unchanged for 14 days (idle_groups_omitted; set includeIdleGroups: true to see them). On an older server those groups are listed; ignore entries that have not changed for weeks.
2. Replay the value
Each firing's replay is a ready set of search-aggregations arguments that recompute the value the alert compared with its threshold. Pass it as is. Then make two changes, one at a time:
- Add
compareTo: "1w"(or"1d"): is the value now different from the same window last week? - Add a
Countexpression next to the alert's function so you know how many rows the value rests on, and setexact: trueif the result saysestimated: trueand the window is a few hours or less.
If config_changed_since is true on a firing, the alert was edited after it fired and the replay uses today's config; say so when you quote the value.
A current state of NoData means the alert's query matches nothing: the service was renamed or stopped emitting. That is the finding.
3. Chronic, noisy, or new?
Decide this before you look for a cause. Start from the firing history's summary:
chronicandchronic_reason:chronic: truemeans it fired on more than 4 of the last 7 days.episodes(start,end,firings,span_minutes,group_by_key,investigation_conversation_id) andepisode_count: firings close together are merged (episode_gap_minutes). One long episode is one problem; ten short ones a day is flapping.flaps,firings_per_day(last 7 days),days_fired(whole lookback) andgroups(firings, first and last per group).value_source: the value at each firing is not stored; it says how to recompute it (pass that firing'sreplaytosearch-aggregations, step 2).time_in_alert_noteexplains why time in Alert is not given.
get-changes shows the same alert as alert_episode rows on the service's timeline, next to deploys and flag changes. On a server without summary, count firings per day from firings yourself.
Other signs of a chronic or noisy alert:
- Many firings in the lookback on the same group, flapping between Warn and Alert.
- The replayed value is close to the
compareTovalue from last week. - The alert evaluates a high percentile over few rows. A P99 over about 600 spans per window is the sixth-slowest span; a handful of slow requests flips it. A
low_sampleblock on the replay says so directly; see "Sampling and percentiles" in launchdarkly-investigate. - The threshold sits inside the normal range of the series: run the replay over 3 to 7 days with
bucketCount24 to 48 and see how often it is crossed.
If it is chronic, say so, show the numbers, and still explain what the tail is made of (one get-trace of the slowest span usually does it). Recommend a concrete retune: a lower percentile (P95), a higher threshold, a minimum count, a longer window, or excluding the group.
If it is new, the onset is the start of the current episode; carry on.
4. Reuse the automatic investigation
If the firing has an investigation_conversation_id, call get-investigation with it as conversationId. Cite its conclusion as its conclusion, and check the key claims against current data before you repeat them. If it says the investigation is not shared with you, do not retry.
5. Investigate the cause
Run steps 3 to 6 of the first five minutes in launchdarkly-investigate, scoped to the alert's query and the onset window:
- What changed:
get-changesfor the service the alert's query names, from 1 to 2 hours before the onset. Pass the alert's ID inalertIdsto see its ownalert_episoderows on the same timeline. - What is over-represented: break the alert's own signal down by
host.name, the version key,http.route, and the flag variation, each withcompareTo. If the alert is grouped, compare the firing group with its sibling groups too. - Evidence: traces, logs and error groups from the slice that stands out.
- Blast radius:
search-service-mapwithinclude: ["health"]for the service.
Load the per-product companion for the alert's product type (below) for the evidence step.
When to load which companion
logs.md: log count or log pattern alertstraces.md: latency, span error-rate, and span-specific alertserrors.md: error-count, new-error-group, crash-rate alertssessions.md: session-health and user-facing-error alertsmetrics.md: OpenTelemetry metric thresholds and composite alerts
If the alert crosses products (a metric alert driven by errors), load both.
Output template
Alert investigations feed notifications and on-call handoffs. Use this structure.
## What triggered
<Alert name and ID, the group that fired, the function and threshold, the replayed value and how many rows it rests on, and when it fired (UTC).>
## Verdict
<New problem, chronic condition, or noise, with the evidence: chronic_reason or firings in the last 7 days, episodes, value vs the same window last week.>
## Likely cause
<Root cause with the numbers: the slice that is over-represented (share of failures vs share of traffic), the change that preceded the onset, and trace IDs, error group IDs and log templates. Say what was ruled out.>
## Scope
<Affected requests, error rate, users or sessions, routes, downstream services, and the time window of impact.>
## Next steps
<1 to 3 concrete actions: roll back version X, turn flag Y off in production, drain host Z, retune the alert to P95 > 1 s with a minimum of 200 spans. If the cause is not established, name the exact query or data that would establish it.>
Guidelines
- Stay tight. No preamble. The on-call should be able to act without re-querying.
- Cite identifiers for every claim: alert ID, group, trace ID, error group secure ID, log timestamp, host, version, flag key.
- Noise is a legitimate outcome. "Fired 133 times in 7 days on the same group; last week's value was higher; the tail is source-map parsing" is a complete answer with a retune recommendation.
- Do not trust an alert's group alone. A grouped alert names where the threshold was crossed, not why. The cause is often in a dimension the alert does not group by, such as one host.
- Convert units. Trace
durationis in nanoseconds; quote thresholds and values in ms or s.
Related skills
- launchdarkly-investigate: the full investigation sequence.
observability/alert-managementandobservability/create-alert(guides served by the LaunchDarkly MCP server; read them withload-skill): retune, mute or rebuild the alert after the diagnosis.