amlint
Semantic linter for Prometheus Alertmanager configs.
amtool check-config validates syntax. amlint validates semantics:
will alerts actually reach a receiver, does the inhibition rule do anything,
are there unreachable routing branches? These are the bugs that burn teams —
config is valid, alerts silently vanish.
Why
Alertmanager configs are YAML routing trees with inhibition rules and receivers. The most painful mistakes are syntactically valid:
- route references a receiver that doesn't exist → alerts are dropped
- inhibition without
equal→ silences unrelated alerts, you think it's quiet, there's actually a fire - catch-all branch before specific ones → specific branches are unreachable
match_rethat doesn't compilegroup_bythat doesn't behave the way you think
amtool won't catch any of this. amlint will.
Install
pip install amlint
Or with Docker (no Python required):
docker run --rm -v $(pwd):/cfg ghcr.io/danikdanik2013/amlint check /cfg/alertmanager.yml
Usage
amlint check alertmanager.yml
amlint check prod.yml staging.yml # multiple files
cat alertmanager.yml | amlint check - # stdin
amlint check alertmanager.yml --strict # WARN also exits non-zero
amlint check alertmanager.yml --format json
amlint check alertmanager.yml --format sarif > results.sarif # GitHub Code Scanning
amlint check alertmanager.yml --ignore empty-receiver,unused-receiver
amlint check alertmanager.yml --only undefined-receiver,bad-regex # run only these checks
amlint check alertmanager.yml --exit-zero # always exits 0, just show findings
amlint diff old.yml new.yml # show what changed
amlint tree alertmanager.yml # visualize the route tree, issues inline
amlint init > alertmanager.yml # generate minimal valid config
amlint list # all check codes with level + description
amlint explain undefined-receiver # detailed explanation + examples
Project config — .amlint.yml or pyproject.toml [tool.amlint]:
# .amlint.yml
ignore:
- empty-receiver
strict: true
severity:
unused-receiver: error # upgrade info → error
# pyproject.toml
[tool.amlint]
ignore = ["empty-receiver"]
strict = true
Shell completions (bash/zsh):
pip install "amlint[completions]"
eval "$(register-python-argcomplete amlint)" # add to ~/.zshrc or ~/.bashrc
Example output:
ERROR Route references receiver 'pager-team' which is not defined in receivers. Alerts matched here will be dropped.
↳ route.routes[1] [undefined-receiver]
WARN Catch-all route (no matchers) with continue:false will intercept all alerts — 2 subsequent sibling(s) are unreachable.
↳ route.routes[0] [unreachable-route]
2 error · 3 warn · 1 info
Exit code 1 on ERROR — ready for CI. --strict makes WARN block too.
CI example
# .github/workflows/lint.yml
- name: Lint Alertmanager config
run: amlint check alertmanager.yml --strict
As a GitHub Action
- uses: actions/checkout@v7
- uses: danikdanik2013/amlint@v0.2.0
with:
config-path: alertmanager.yml
strict: true
No pip install step needed. Supports --ignore/--only via inputs and optional SARIF upload
for PR annotations — see the GitHub Action docs.
Checks
| code | level | what it catches |
|---|---|---|
undefined-receiver |
error | route references a receiver that doesn't exist |
bad-regex |
error | match_re pattern fails to compile |
no-root-route |
error | no root route defined |
duplicate-receiver |
error | receiver name defined more than once |
undefined-time-interval |
error | mute_time_intervals / active_time_intervals references unknown interval |
email-no-smarthost |
error | email_configs without smarthost and no global SMTP |
webhook-no-url |
error | webhook_configs without url or url_file |
pagerduty-no-routing-key |
error | pagerduty_configs without routing_key |
slack-no-api-url |
error | slack_configs without api_url and no global |
opsgenie-no-api-key |
error | opsgenie_configs without api_key and no global |
msteams-no-webhook-url |
error | msteams_configs without webhook_url |
telegram-no-bot-token |
error | telegram_configs without bot_token |
discord-no-webhook-url |
error | discord_configs without webhook_url |
victorops-no-api-key |
error | victorops_configs without api_key and no global |
wechat-no-corp-id |
error | wechat_configs without corp_id and no global |
sns-no-target |
error | sns_configs missing topic_arn/phone_number/target_arn |
template-file-missing |
error/warn | templates: references a file that doesn't exist |
inhibit-no-equal |
warn | inhibition without equal silences too broadly |
unreachable-route |
warn | catch-all hides subsequent sibling routes |
groupby-ellipsis |
warn | ... mixed with explicit labels in group_by |
repeat-before-group |
warn | repeat_interval shorter than group_interval |
circular-inhibition |
warn | two inhibition rules that silence each other |
wait-exceeds-interval |
warn | group_wait longer than group_interval |
empty-receiver |
warn/info | receiver has no integration configured — alerts will be dropped |
unused-receiver |
info | receiver defined but not used in any route |
inhibit-same-match |
info | source and target match the same label value |
useless-continue |
info | continue:true on the last sibling route has no effect |
deep-nesting |
info | route tree deeper than 5 levels |
global-resolve-timeout-missing |
info | global.resolve_timeout not set; defaults to 5m |
route-match-collision |
warn | two sibling routes with identical matchers |
Run amlint explain <code> for detailed description and examples of any check.
Tests
python3 -m pytest test_linter.py -v
License
MIT
Metadata
Release files for amlint 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| amlint-0.2.0.tar.gz | 22.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| amlint-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.8 kB
Release files / amlint-0.2.0.tar.gz
| Download URL | amlint-0.2.0.tar.gz |
|---|---|
| Size | 22.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7f15304f9889042ebfd7bb733d57a2bfbe99011f8ffc303f2687d1c1f54a49d5
|
|
BLAKE2b-256 checksum How to use checksums |
c23285bb7c105a3e84fcbf506968d6c07046bc2b117d584ea827a4fbc8067956
|
| 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 Aug 16, 2026.
Transparency logRelease files / amlint-0.2.0-py3-none-any.whl
| Download URL | amlint-0.2.0-py3-none-any.whl |
|---|---|
| Size | 22.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eec23105c12dfacc31f9a813e3c157ec5021d3794c96c1112d45f22f8e011a24
|
|
BLAKE2b-256 checksum How to use checksums |
8e14a4a83fe4ca88a9a2c9c74ca4d3359292a8a454000a0bf15f374a64817e10
|
| 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 Aug 16, 2026.
Transparency log