--explain — Claude-augmented alerts¶
Scherlok's alerts tell on-call what broke. --explain adds why it
probably broke and what to check next, generated by Claude from the same
aggregate data the alert already contains.
pip install 'scherlok[explain]'
export ANTHROPIC_API_KEY=sk-ant-...
scherlok watch --webhook https://hooks.slack.com/services/... --explain
The flag exists on watch, ci, check, dbt, and dbt-run-and-watch.
How it works¶
- A watch run fires N anomalies.
- Scherlok builds one bundle (see below) for the whole batch and makes
one API call — not one per anomaly — so cost stays bounded and the
model can correlate across tables ("all four anomalies trace back to
raw_orders"). - The structured hypothesis is injected into every alert channel of that run:
- Slack — a colored attachment under the alert blocks
- Discord / Teams — appended text
- Generic JSON webhook — a machine-readable
explanationfield - Email — a highlighted block under the anomaly table
- Console — a panel under the anomaly table
scherlok dbt --output json— anexplanationkey on the JSON document (orexplanation_errorwhen the call failed)
The response is forced through a tool call with a fixed schema, so the output always has the same three fields:
{
"summary": "All four anomalies trace back to stg_orders — upstream raw_orders likely changed shape.",
"likely_cause": "Schema drift on stg_orders propagated downstream; the new enum value suggests a source-side change.",
"diagnostic_steps": [
"Diff raw_orders DDL against last week (git log on the source migration).",
"Run `scherlok investigate raw_orders` to confirm the schema fingerprint changed."
]
}
The bundle (what is sent)¶
Aggregates only — built by scherlok.explainer.build_bundle():
| Section | Contents |
|---|---|
anomalies |
{table, type, severity, message} — the same strings already in your alert |
lineage |
dbt runs only: up to 10 upstream parent names per anomalous model (2 hops from manifest.json) |
recent_history |
up to 20 same-table anomalies from the local store, last 7 days ({table, type, severity, detected_at}) |
meta |
adapter name, run timestamp, --fail-on setting |
Never sent: warehouse rows, cell values, column contents, connection
strings, credentials. Scherlok's profile store does not capture those in the
first place, and the bundle builder strips anomaly dicts down to the four
fields above — tests/test_explainer.py pins this as a contract.
Configuration¶
| Env var | Default | Purpose |
|---|---|---|
ANTHROPIC_API_KEY |
— (required) | API key, resolved server-side only |
SCHERLOK_EXPLAIN_MODEL |
claude-haiku-4-5-20251001 |
Model override |
Cost¶
One call per fired run on Claude Haiku 4.5: roughly 1–2K input tokens plus ~200 output tokens — well under a cent per run (~$0.003). Runs with zero anomalies make no API call at all.
Failure mode: fail-open, always¶
--explain never blocks the original alert. If the call fails — missing
key, missing package, timeout, rate limit, malformed response — the
unaugmented alert is delivered as usual with a one-line note:
(--explain unavailable: ANTHROPIC_API_KEY is not set)
The alert path has a 15-second timeout and a single retry, so a degraded API can delay an alert by at most ~30 seconds and can never suppress it. Injected text is escaped and truncated per platform (Slack 3000-char section limit, Discord 2000-char content limit), and when the remaining budget is too small the hypothesis is dropped entirely — the original alert always wins.
How to turn it off¶
It is off by default. Don't pass --explain and no code path touches the
anthropic package (the import is lazy).