Skip to content

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

  1. A watch run fires N anomalies.
  2. 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").
  3. The structured hypothesis is injected into every alert channel of that run:
  4. Slack — a colored attachment under the alert blocks
  5. Discord / Teams — appended text
  6. Generic JSON webhook — a machine-readable explanation field
  7. Email — a highlighted block under the anomaly table
  8. Console — a panel under the anomaly table
  9. scherlok dbt --output json — an explanation key on the JSON document (or explanation_error when 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).