<p align="center" <img src="https://raw.githubusercontent.com/allenhori/zhao-cli/master/docs/assets/logo.png" alt="zhao logo" width="128" </p
zhao (曌)
Named for the character Empress Wu Zetian invented for herself: 明 (sun and moon) over 空 (sky) — "illuminating everything below." A free, deterministic breaking-change gate for dbt. zhao reads your dbt project's compiled SQL and tells a reviewer exactly what a pull request changed and which downstream models it actually reaches — before anyone has to trace the DAG by hand.
Changed:
model model.jaffle_shop.stg_customers:
- column removed: last_name
Downstream impact:
model model.jaffle_shop.dim_customers:
[BREAKING] last_name removed from model model.jaffle_shop.stg_customers breaks reference via last_name (column-removed-with-active-references)
Summary: 1 model(s) changed, 1 column(s) changed, 1 breaking, 0 warning
Impacted models: dim_customersWhy
dbt's own state:modified comparison is syntactic: any compiled-SQL text change counts as "modified," and everything downstream is assumed affected. Teams end up either rebuilding their whole downstream cone on every PR (slow CI), or leaning on a human reviewer to catch a removed column, a narrowed type, or a loosened join by reading SQL — something nobody reliably does across a DAG of any real size.
zhao parses the SQL itself and computes real column-level lineage between two states of your project, classifies each change against a fixed Rule catalog (column removed with an active reference, a column's logic changed, type narrowed, join loosened, column added), and reports the exact models each change actually reaches — never the whole DAG, never a guess. Change one column's logic and only the models that read that column, directly or through a column derived from it, are reported; a model that merely sits downstream of the same parent but reads other columns is not. The analysis itself is entirely local: no LLM, no account, and it never reads or sends your actual data — nothing installed in your warehouse beyond what dbt run already needs. The one place a network call happens is resolving a git-native Baseline (dbt compile/dbt deps, the same as running dbt yourself) — skip that entirely by passing --state with an already-compiled manifest, for a genuinely zero-network-call run.
Install
macOS / Linux — Homebrew:
brew install allenhori/zhao/zhao-cliWindows — Scoop:
scoop bucket add zhao https://github.com/allenhori/zhao-scoop
scoop install zhao-cliPython / dbt projects — PyPI, with uv or pip:
uv tool install zhao-cli # or: pip install zhao-cliA pre-built binary in a wheel, no Rust toolchain needed. Handy for pinning zhao next to dbt-core in your project's own dependencies (e.g. zhao-cli==X.Y.Z), so local dev and CI get the same version.
Any platform — the install script:
curl -fsSL https://raw.githubusercontent.com/allenhori/zhao-cli/master/scripts/install.sh | shDownloads the right pre-built binary for your platform from the releases page — no Rust toolchain needed. Windows without Scoop: grab zhao-x8664-pc-windows-msvc.zip from the same page. Rust users: cargo install zhao-cli (via crates.io), or cargo install --git https://github.com/allenhori/zhao-cli to build directly off master instead of the last tagged release.