Three alerting outages from one root: a name where an ID belonged
Someone renamed a Slack channel and alert delivery for a production system died silently. It was the third failure on the same identifier — every earlier fix changed the transport and kept the name.
At Gambchamp alert delivery for a tier-0 production system was completely dead, and nothing said so. Someone had renamed the Slack channel. Alertmanager kept posting to the old name, and Slack answered channel_not_found every ten seconds. Two live alerts sat in the queue the whole time. The only outward sign was alertmanager_notifications_failed_total — a counter nobody watched.
The third time on the same identifier
It was the third failure in a row on the same thing:
- first, a shared incoming webhook ignored the
channelfield, so alerts landed in a shared channel. Fix: a dedicated Alertmanager; - then, a bot token honoured
channeland delivery worked — but it was bound to the channel name. It looked solved; - finally, the channel was renamed and delivery died.
Each fix changed the transport and left the binding untouched, so the class of failure survived every repair.
A label is not an identifier
A Slack channel name is a human label: its owner can change it at any time without telling anyone. The channel ID is issued once and never changes — not on rename, not on a privacy switch. Our config referenced the label.
Two things made the failure silent rather than loud. Slack marks channel_not_found as unrecoverable, so Alertmanager does not retry; notifications are dropped, not delayed. And a dead delivery path looks exactly like a quiet system. Nothing fires when nothing can fire.
The fix
- Pin the channel by ID in the receiver. The channel is private and the bot has no permission to list it, so the API cannot validate the ID read-only. I validated it with a scheduled message that was deleted before it was ever delivered — a probe that resolves the ID without posting anything.
- Route by an explicit label, not by name. When the same system’s alerts were split into an application channel and an infrastructure channel, the obvious regex on the alert-name prefix would have sent five RDS guardrails — replication slots, storage — into the application channel. A
signallabel set in the rule group lets each rule declare its own layer instead of depending on how someone named it.
After the change I checked that the configuration was actually re-read, not just synced, and watched the derivative of the counters: failed notifications to zero, sent notifications rising. Cumulative counters on their own say nothing.
Takeaways
- Anything a human can rename is a label, not an identifier. Bind to the immutable handle — an ID — or to an explicit declaration, such as a label the author sets on purpose.
- Test the delivery path, not just the component. A healthy Alertmanager that cannot deliver is indistinguishable from having no alerts.
- Alert on
increase(alertmanager_notifications_failed_total[5m]) > 0. It is the cheapest dead-man switch you will ever add. - Audit the same class elsewhere: consumer group IDs built from pod names, exporters filtering by naming convention, policies matching objects by name.