Skip to main content

amlint

CI Docs Coverage PyPI Python Docker Verified against Alertmanager

A linter for Prometheus Alertmanager configs that finds the bugs nothing else complains about.

amtool check-config tells you whether your config is valid. amlint tells you whether it does what you meant: whether every alert reaches someone, whether a route can ever match, whether an inhibit rule mutes far more than you think. These configs load fine, Alertmanager runs without a single warning, and alerts quietly go to the wrong place, or nowhere.

Try it in your browser → Paste a config and see the problems, the route tree and where any alert would go. It runs entirely in the page, so your config never leaves your machine.

What it looks like

# alertmanager.yml
route:
  receiver: default
  routes:
    - receiver: slack-all            # no matchers: catches every alert
    - receiver: pager
      matchers: [severity="critical"]
inhibit_rules:
  - source_matchers: [severity="critical"]
    target_matchers: [severity="warning"]   # no `equal`
receivers:
  - name: default
    webhook_configs: [{url: "http://hooks.internal/default"}]
  - name: slack-all
    slack_configs: [{api_url: "https://hooks.slack.com/x", channel: "#alerts"}]
$ amlint check alertmanager.yml

  ✖  ERROR  [undefined-receiver]  ·  alertmanager.yml:5:7  ·  route.routes[1]
     Route references receiver 'pager' which is not defined in receivers.
     Alertmanager refuses to load this config.

  ⚠  WARN   [inhibit-no-equal]  ·  alertmanager.yml:8:5  ·  inhibit_rules[0]
     Inhibition rule has no 'equal' field: it will silence alerts across
     unrelated firing sources (no shared label binding them). Almost always a mistake.

  ⚠  WARN   [unreachable-route]  ·  alertmanager.yml:4:7  ·  route.routes[0]
     Catch-all route (no matchers) with continue:false will intercept all alerts
     — 1 subsequent sibling(s) are unreachable.

  ℹ  INFO   [global-resolve-timeout-missing]  ·  alertmanager.yml:1:1  ·  global
     global.resolve_timeout is not set, so Alertmanager uses 5m. It only applies
     to alerts sent without an end time; Prometheus always sets one.

  ✖ 1 error  ·  ⚠ 2 warnings  ·  ℹ 1 info

In plain words: the pager receiver doesn't exist, so Alertmanager won't even load this. Fix that, and the first route still catches every alert, so the critical route is never reached. And one critical alert anywhere mutes every warning everywhere, because the inhibit rule doesn't say which alerts belong together. amtool would flag only the first problem.

Every finding points to file:line:col. The exit code is 1 on any error, so CI fails.

Install

pip install amlint

Other ways:

# Docker, no Python needed
docker run --rm -v "$PWD":/cfg ghcr.io/danikdanik2013/amlint check /cfg/alertmanager.yml

# Homebrew
brew tap danikdanik2013/amlint https://github.com/danikdanik2013/amlint
brew install amlint

Or don't install anything and use the playground.

In your editor: install the amlint extension for VS Code, or set up Neovim or Helix. You see findings as you type, and a hover on each route shows where its alerts go.

Using an AI coding assistant? In Claude Code, run /plugin marketplace add danikdanik2013/amlint and then /plugin install amlint@amlint. Claude will then use amlint to lint your configs and answer "where does this alert go?". Other assistants can read llms.txt.

What you can do with it

I want to… Run
Find problems in a config amlint check alertmanager.yml
Fail CI on warnings too, not only errors amlint check alertmanager.yml --strict
See where an alert would go amlint route alertmanager.yml team=db severity=critical
Check whether an alert gets muted amlint route alertmanager.yml alertname=DiskFull --firing alertname=NodeDown
Write unit tests for routing amlint test alertmanager.yml tests.yml
See what a change does to routing amlint diff old.yml new.yml
Look at the route tree amlint tree alertmanager.yml
Draw the route tree as a Mermaid diagram amlint tree alertmanager.yml --format mermaid
Catch routes for alerts that don't exist amlint check alertmanager.yml --rules 'rules/*.yml'
Lint what you deploy to Kubernetes amlint check alertmanager-secret.yaml or kubectl kustomize deploy/ | amlint check -
Learn what a check means and how to fix it amlint explain shadowed-route
See every check amlint list
See findings in VS Code, Neovim or Helix as you type amlint lsp, started by the editor (setup)

Full guide: usage docs.

What it catches

38 checks. 18 of them catch things amtool doesn't, because the config is valid and simply behaves differently than you expect:

  • Alerts that reach nobody. A route points at a receiver that has no integration, or a catch-all route sits above more specific ones so they never match.
  • Routes that can never match. An earlier sibling route catches every alert first. This can be an exact duplicate (route-match-collision) or a broader one, such as severity=~"warning|critical" before severity="critical" (shadowed-route).
  • Inhibit rules that mute too much. No equal labels, rules that mute each other, or source and target that match the same alert.
  • Timings that contradict themselves. group_wait longer than group_interval, or repeat_interval shorter than group_interval.
  • Matchers Alertmanager reads differently than you do. For example, code=~[0-9]{3} without quotes loses its closing brace.
  • Routes for alerts that don't exist (with --rules). A typo like HighCpuUsage vs HighCPUUsage silently kills a branch.

The other 20 checks repeat what amtool already rejects: undefined receivers, missing webhook URLs or API keys, bad regexes. They're there so one tool gives you one report, in one format.

All checks with examples →

Test your routing like code

Write down which alerts should go where, and let CI tell you when that stops being true:

# tests.yml
- name: critical infra alerts page infra and the on-call
  labels: {team: infra, severity: critical}
  expect: [infra-pager, pager]        # any order
- name: unknown teams fall back to the default
  labels: {team: unknown}
  expect: [default]
- name: disk alerts are muted while their node is down
  labels: {alertname: DiskFull, instance: db-1}
  firing:                             # other alerts firing at the same time
    - {alertname: NodeDown, instance: db-1}
  inhibited: true
amlint test alertmanager.yml tests.yml                  # fails with a diff if a case breaks
amlint test alertmanager.yml tests.yml --fail-under 90  # and if tests reach < 90% of routes

amlint diff old.yml new.yml works the other way round. You don't write any cases. It compares two versions and lists every kind of alert that now goes somewhere else, for example team=db, severity=critical: pagerduty-db → slack-db.

Can I trust the simulation?

amlint simulates routing and inhibition in Python. To prove it behaves like the real thing, a CI job generates thousands of configs and alerts and runs them through amtool and a running Alertmanager, v0.32.2, v0.33.1 and v0.34.1. It checks that both give the same receivers, the same inhibitions and reject the same configs. The few known differences are listed on that page.

Use it in CI

GitHub Action. Findings show up as annotations on the PR diff:

- uses: actions/checkout@v7
  with:
    fetch-depth: 0
- uses: danikdanik2013/amlint@v0.6.1
  with:
    config-path: alertmanager.yml
    strict: true
    diff-base: ${{ github.event.pull_request.base.sha }}   # optional: post a routing diff comment

With diff-base the action also posts one PR comment, updated on every push. It shows which alerts now reach different receivers and which findings are new or fixed. It needs permissions: pull-requests: write. Action docs →

Anywhere else: pip install amlint && amlint check alertmanager.yml --strict. Use --format github, --format sarif (GitHub Code Scanning), --format json, or amlint diff --format markdown for a PR comment.

pre-commit:

repos:
  - repo: https://github.com/danikdanik2013/amlint
    rev: v0.6.1
    hooks:
      - id: amlint        # runs on files ending in alertmanager.yml or alertmanager.yaml

Configure it

Turn checks off or change their severity for the whole project, in .amlint.yml or pyproject.toml:

# .amlint.yml
ignore: [empty-receiver]
strict: true
severity:
  unused-receiver: error

Or silence one spot, with the reason right next to it:

# amlint: disable-file=global-resolve-timeout-missing   ← whole file

receivers:
  - name: blackhole   # amlint: disable=empty-receiver   ← this line

Kubernetes, Helm and Mimir

Point amlint at the file you actually deploy. It detects the format and finds the Alertmanager config inside:

  • a Kubernetes Secret or ConfigMap with alertmanager.yaml
  • kube-prometheus-stack Helm values (alertmanager.config)
  • a Mimir or Cortex tenant config (alertmanager_config)
  • prometheus-operator AlertmanagerConfig resources, merged the way the operator does it

Line numbers still point into your file. Details →

How is this different from amtool?

amtool amlint
Rejects invalid configs (undefined receiver, bad regex, missing API key) ✅ ✅
Finds valid configs that misroute or over-mute alerts — ✅
Where does one alert go? ✅ ✅
A file of routing tests, run in CI, with route coverage — ✅
Inhibition: is this alert muted while that one fires? — ✅
What does this change do to routing? — ✅
Kubernetes / Helm / Mimir / AlertmanagerConfig input — ✅
PR annotations, SARIF, JSON, Markdown — ✅
Needs a Go binary ✅ —

They work well together. If you already run amtool check-config in CI, keep it, and add amlint for everything after "is it valid?".

Metadata

Release files for amlint 0.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for amlint 0.6.1
File Size Uploaded
amlint-0.6.1.tar.gz 97.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for amlint 0.6.1
File Interpreter ABI Platform
amlint-0.6.1-py3-none-any.whl Python 3 none any Details

Total release size: 196.5 kB

Release files / amlint-0.6.1.tar.gz

Download URL amlint-0.6.1.tar.gz
Size 97.8 kB
Tags Source
SHA-256 checksum
How to use checksums
7a456705987661464abb1486a0cb58c26160008b5b8c7ff905e26ff37b44e769
BLAKE2b-256 checksum
How to use checksums
c1be849d038eb2b73f5f7f8d1a43c33fd5d6d717f3b3b9ba7667dd34903a7395
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / amlint-0.6.1-py3-none-any.whl

Download URL amlint-0.6.1-py3-none-any.whl
Size 98.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4cde98688bfaf9019eae9ef83be51f3ace8ab9c3a5dad1ed55001198b479b4da
BLAKE2b-256 checksum
How to use checksums
88297758a14e905fdf0e74cf19d1e945dd2cf4ed2b95a9b61bf0e4c2370018f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.1 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.2.0

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page