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.

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.0
  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.0
    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.0

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.0
File Size Uploaded
amlint-0.6.0.tar.gz 97.5 kB Details

Built distribution (wheel)

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

Total release size: 196.1 kB

Release files / amlint-0.6.0.tar.gz

Download URL amlint-0.6.0.tar.gz
Size 97.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6a68a40119b1209bb9d02d520a0ec9481da55da1457771afa874478b0e8a86ff
BLAKE2b-256 checksum
How to use checksums
7ecddda0eadca70b3d73d99e410a53afb8e9080a749a5521815df5911439014c
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.0-py3-none-any.whl

Download URL amlint-0.6.0-py3-none-any.whl
Size 98.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fcbfc892752696037127733edde376805986fd28f4aaf1c9f864cb2461b44061
BLAKE2b-256 checksum
How to use checksums
0bf578b3d57ad66f0dfd014cafbeaaf2629be0564caaffd0c28d9087cf4c7065
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

0.6.1

2 release files

This release

0.6.0 This release

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