Stale alerts analyzer
Skill conallob/o11y-analysis-tools/skills/stale-alerts-analyzer
Query a live Prometheus's historical ALERTS series to find alert rules that haven't fired in a long time — "dead code analysis" for alerting rules, surfacing candidates for deletion or threshold review. Use interactively during alert-quality/on-call hygiene reviews, not as a CI gate. Requires a live Prometheus with sufficient ALERTS retention.From its SKILL.md
npx -y skills add conallob/o11y-analysis-tools --skill stale-alerts-analyzerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 4 commands, including `go build -o bin/stale-alerts-analyzer ./cmd/stale-alerts-analyzer` and 3 more.
What its file declares
Copied from the file, not written here
The file declares its own license as BSD-3-Clause. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
5.0 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
stale-alerts-analyzer
Cross-references every alert: name defined in a rules file against
Prometheus's historical ALERTS series to find ones that haven't fired
within a configurable time horizon. Functionally this is dead-code
analysis applied to alerting rules: an alert nobody has seen fire in a
year is either (a) protecting against something that no longer happens,
(b) has a threshold too conservative to ever trigger, or (c) is genuinely
still needed and just hasn't had cause to fire — this tool flags
candidates, a human makes the call.
When to use this skill
- Periodic alert hygiene / on-call load review: "which of our alerts are actually doing anything?"
- Before a large alerting-rules refactor, to identify safe-to-remove dead weight.
- Not a CI gate: it depends on live, non-reproducible production
history, and "delete this alert" is a decision that deserves a human
in the loop, even when using
--fix.
Prerequisites
- A reachable Prometheus (or Prometheus-API-compatible Thanos/Cortex/
Mimir) that has actually been evaluating the rules file's alerts, so
ALERTShas history. - Retention vs.
--timehorizonmatters just as much as foralert-hysteresis: an alert reported "stale (no firings in 380 days)" is meaningless if Prometheus only retains 30 days — the tool has no way to distinguish "never fired" from "fired outside what Prometheus still remembers." Confirm retention before trusting a long horizon.
Setup
go build -o bin/stale-alerts-analyzer ./cmd/stale-alerts-analyzer
# or: go install github.com/conallob/o11y-analysis-tools/cmd/stale-alerts-analyzer@latest
Usage
stale-alerts-analyzer [options]
| Flag | Default | Effect |
|---|---|---|
--prometheus-url | http://localhost:9090 | Prometheus API base URL. Required. |
--rules | (required) | Path to the rules YAML file whose alert names to check. |
--timehorizon | 12M | Staleness lookback window. Accepts Go durations (h, m, s) and extended units: d (days), w (weeks), M (30-day months), y (365-day years) — e.g. 90d, 6M, 1y. |
--fix | false | Delete the identified stale alerts directly from the rules file. |
--verbose | false | Print query details while fetching. |
Note: unlike the other tools, there is no --output=json flag despite
being mentioned in the top-level README's examples — as currently
implemented, output is always the human-readable report below; there is
no machine-readable export mode. Don't tell a user to pipe --output=json
expecting it to work.
Typical invocations
# Default: alerts with no firings in the last 12 months
stale-alerts-analyzer --prometheus-url=http://prometheus:9090 --rules=./alerts.yml
# Shorter horizon in days
stale-alerts-analyzer --prometheus-url=http://prometheus:9090 --rules=./alerts.yml --timehorizon=90d
# Delete stale alerts directly (review the diff before committing!)
stale-alerts-analyzer --fix --prometheus-url=http://prometheus:9090 --rules=./alerts.yml --timehorizon=1y
Reading the output
Lists active alerts (fired within the horizon, with last-fired time and
age) separately from stale alerts (last-fired time or "Never" within the
lookback), followed by a summary with total/active/stale counts and a
never-fired-vs-fired-but-stale breakdown. Exit code 1 when stale alerts
exist and --fix wasn't passed (so the run can be used as a nudge, even
if you don't want it as a hard CI gate); 0 when nothing is stale or
after a successful --fix.
Agent workflow
- Run without
--fixfirst and read the stale list with the user — "stale" here is a signal to investigate, not an automatic deletion order. A dead-man's-switch-style alert (e.g.Watchdog) is expected to never "stale-fire" in the sense this tool measures, since it fires continuously rather than transiently; don't recommend deleting continuously-firing meta-alerts just because they show odd stats. This tool has no built-in exclude list (despite an--excludeflag being mentioned in the top-level README) — as currently implemented there is no such flag, so filter candidates yourself before recommending deletion. - For each stale candidate, distinguish "never fired" from "fired, but outside the horizon" using the printed breakdown — the former is a stronger deletion signal than the latter.
- Cross-check
--timehorizonagainst the target Prometheus's actual retention before treating a "stale" verdict as reliable. - Prefer proposing a deletion PR for human review over
--fixunless explicitly asked to apply it directly — removing an alert is a coverage-reducing change.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most monitoring observability skills give in ~1.2k tokens
Counted across 530 of the 532 authors here whose files we hold, read 2026-09-06
- Use structured JSON loggingin 40 of 530, across 36 files
- Link every alert to a runbookin 29 of 530, across 27 files
- Attach correlation IDs to every log linein 19 of 530, across 16 files
- Alert on symptoms rather than causesin 19 of 530, across 17 files
- Use OpenTelemetry for distributed tracingin 15 of 530, across 14 files
- Alert on symptoms users feelin 15 of 530, across 13 files
- Implement health check endpointsin 14 of 530, across 10 files
- Inspect existing dashboards firstin 12 of 530, across 4 files
- Build the minimum useful boardin 12 of 530, across 4 files
- Start from operator questionsin 12 of 530, across 4 files
- Propagate trace context across boundariesin 11 of 530, across 10 files
- Include trace id in all log entriesin 10 of 530, across 9 files
Said here and by no other author read
- Run without fix first and read the stale list
- Distinguish never fired from fired outside the horizon
- Cross-check time horizon against Prometheus actual retention
- Prefer proposing a deletion PR over using fix
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.