Alerts that go unactioned reduce the team’s responsiveness to future alerts — the first genuine critical failure may be missed when it arrives. The design question for any pipeline alerting system is how to route the right alert to the right person at the right urgency level.
The Core Problem: Alert Fatigue
Most monitoring setups start with a single channel that receives everything: test failures, warnings, cost anomalies, successful run confirmations. Within a few weeks, the channel is unread. The team has learned that most messages don’t require action. When the one message that does require action arrives, it looks identical to the thirty that didn’t.
Alert fatigue is solved by reducing volume and increasing signal quality. The two levers:
- Route by severity — different urgency levels go to different destinations
- Reduce false positives — alerts that fire on expected or acceptable conditions get tuned out or removed
The broken window principle applies: when the team tolerates chronically firing alerts, they accept more. The discipline is enforced by making every alert either actionable or removed.
Tiered Routing Structure
A three-tier model works for most small-to-medium analytics teams:
| Level | Condition | Destination |
|---|---|---|
| Info | All tests pass | #data-status Slack channel (brief summary) |
| Warning | Minor failures or cost anomalies | DM to responsible engineer |
| Critical | Production model failures, large cost spikes | Both Slack channel AND Telegram |
The “all tests pass” message is worth keeping, even though it sounds like noise. It provides negative confirmation — you know the monitoring ran and found nothing. A gap in messages is ambiguous: did everything pass, or did the monitoring job fail silently? A brief ”✅ All 147 tests passed — 2026-03-27 07:02 CET” takes two seconds to read and confirms the system is working.
With OpenClaw’s cron scheduler, this requires separate jobs for different thresholds, or a skill that routes its own output based on what it finds. The separate-jobs approach is easier to get right:
# Job 1: Daily summary — always posts to team channelopenclaw cron add --name "dbt-daily-summary" \ --cron "0 7 * * *" \ --tz "Europe/Paris" \ --session isolated \ --message "Run dbt test. Post a brief summary to the team channel regardless of result. Include pass/fail counts." \ --announce \ --channel slack \ --to "channel:C_DATA_STATUS_CHANNEL"
# Job 2: Failure escalation — only meaningful if you add severity logic to the skill# (or configure the agent to DM only when failures exceed a threshold)The single-skill routing approach — teaching one agent to decide where to send its output based on severity — is possible but fragile. The agent has to correctly assess severity and then take the right routing action. Separate jobs are more predictable: each job has a clear purpose and a fixed destination.
Slack vs. Telegram
OpenClaw supports 15+ messaging channels. For data pipeline monitoring, the practical choice is between Slack and Telegram.
Slack is the natural choice for teams already using it as their primary communication tool. Key advantages for monitoring:
- Threading lets you keep failure discussions organized. The initial alert goes in a channel; investigation happens in the thread.
- Channel routing (
--to "channel:C1234567890") separates monitoring noise from team conversation. A#data-alertschannel that people only check when something breaks is a healthier setup than posting alerts into#general. - DM delivery (
--to "user:U1234567890") routes personal alerts to individuals without broadcasting to the whole team. Useful for “this is your model, this is your problem” routing. - Slack’s mobile app means you’ll see alerts on your phone when you’re not at a desk.
The Slack channel ID (format C1234567890) appears in the channel’s URL on the web or in the channel details panel. The user ID (U1234567890) appears in a user’s profile.
Telegram fills a different role. Because it’s separate from work communication, Telegram alerts are harder to miss — they don’t get buried under the accumulated Slack noise of a busy workday. Some engineers set up Telegram specifically for critical alerts that need to reach them even if they’ve muted Slack for focus time.
The practical pattern: use Slack for team-visible alerts and Telegram as a personal backup channel for critical failures. Configure Telegram alerts for conditions where you need to know immediately regardless of whether you’re checking work apps.
# Critical failure: both Slack and Telegramopenclaw cron add --name "bq-cost-anomaly-check" \ --cron "0 8 * * *" \ --tz "Europe/Paris" \ --session isolated \ --message "Query BigQuery INFORMATION_SCHEMA for cost anomalies in the last 24 hours. If today's spend exceeds 2x the 7-day average, alert with warehouse details." \ --announce \ --channel slack \ --to "channel:C_DATA_ALERTS"
# Same job posting to Telegram for personal backupopenclaw cron add --name "bq-cost-anomaly-telegram" \ --cron "0 8 * * *" \ --tz "Europe/Paris" \ --session isolated \ --message "Query BigQuery INFORMATION_SCHEMA for cost anomalies in the last 24 hours. If today's spend exceeds 2x the 7-day average, send a brief summary. Skip if no anomaly." \ --announce \ --channel telegram \ --to "chat:YOUR_TELEGRAM_CHAT_ID"This double-posting approach means the Telegram job fires even when there’s no anomaly, which wastes a small amount of API cost. Teach the skill to send nothing when conditions are normal, or accept the cost as worthwhile for the reliability guarantee.
Delivery Modes
Beyond the channel selection, each OpenClaw cron job has three output delivery modes:
Channel announce (--announce): Posts the agent’s response to the specified messaging channel. This is the standard monitoring mode — results go to Slack or Telegram where humans can see them.
Webhook POST: Sends structured data to an HTTP endpoint. Use this when you want to pipe results into another system rather than (or in addition to) a messaging channel. Practical use cases:
- Feed failure counts to a PagerDuty alert
- Post structured JSON to a custom monitoring dashboard
- Trigger a downstream automation when specific conditions are met
The webhook receives the agent’s output as a POST request body. Format it as JSON in the skill instructions if the receiving system expects structured data.
Silent / log-only: Results are logged locally without sending anything externally. Use this while testing a new job configuration. You can review what the agent would have sent before exposing it to your team channel.
The typical progression when setting up a new monitoring job: start silent, confirm the output looks right, switch to announce.
What Makes an Alert Actionable
The difference between a useful alert and one that creates anxiety comes down to three questions the reader can answer immediately:
- What broke? (which model, which warehouse, which job)
- How bad is it? (failing rows vs. total rows, cost multiple vs. average)
- What should I do? (check source freshness, investigate this warehouse, no action needed)
An alert that answers all three can be triaged in 30 seconds without opening a laptop. An alert that only answers the first question — “these tests failed” — requires investigation before you know whether it’s urgent.
The skill file is where you build this context into the agent’s instructions. The delivery pattern just determines where the result lands. Even perfect routing won’t save an alert that doesn’t include enough information to act on.
Suppressing Non-Actionable Alerts
Some conditions fire regularly but don’t require action: tests that fail during a known maintenance window, cost spikes that correspond to a scheduled heavy job, warning-severity tests on models under active development. Alerting on these creates noise without value.
The options for suppression:
Teach the skill to filter conditions. “If the only failures are in the dev_ schema, do not send an alert. These are expected during development.” This relies on the agent consistently following the instruction, which works most of the time but not always.
Time-window exclusions. Schedule the monitoring job to run outside maintenance windows. If your weekly full-refresh job runs Sunday midnight to 3 AM and generates expected failures, schedule the monitoring cron for 7 AM Monday when the maintenance window is over.
Separate monitoring jobs for separate purposes. Don’t try to handle production monitoring, development monitoring, and cost monitoring in a single job. Separate jobs with separate skill configurations are easier to tune independently and suppress selectively.
The goal is a monitoring setup where every alert that fires represents something worth looking at. When that bar is met, alert response rates go up and the team trusts the system. When it’s not met, the whole system becomes background noise.
Comparison to Dedicated Observability Tools
For dbt-specific monitoring, Elementary handles alert routing natively with more granular controls: per-model channel routing, suppression intervals for repeated failures, and integration with the Elementary report for historical context. See Elementary alert routing with filters for the specifics.
The OpenClaw approach described here is complementary, not competitive. Elementary gives you deep dbt observability with anomaly detection and historical trending. OpenClaw gives you a flexible alerting layer that can cover systems Elementary doesn’t — BigQuery job failures, Snowflake costs, arbitrary shell commands — and can present results in natural language rather than raw log output. See Data Observability Build vs. Buy for where each approach fits in the broader stack.