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_customers
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, type narrowed, join loosened, column added), and reports the exact models
each change actually reaches — never the whole DAG, never a guess. 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.
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: grab zhao-x86_64-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.
Two release channels: a tagged stable release (v0.1.0, ...) for anything you depend
on, and a rolling nightly build off master, always available at the
nightly tag — set
ZHAO_VERSION=nightly before running the install script above to track it instead. See
RELEASING.md for how the two channels work and how releases are cut.
cd your-dbt-project
dbt compile
zhao check --against mainzhao check finds the merge-base between your branch and main, compiles it with dbt to
get a Baseline, diffs it against your current state, and exits non-zero if anything breaking
fired. Full walkthrough: Getting started.
| Command | What it does |
|---|---|
zhao check |
The CI gate — diffs against a Baseline, fails on a breaking change. |
zhao diff |
Same engine, always exits 0 — for local inspection during development. |
zhao lineage |
What's upstream/downstream of a model or column, right now (no diff, no git). |
zhao update |
Replaces the current binary with a release from GitHub Releases. The only command that reaches the network at all — and only to download the binary itself, never to send anything from your project. See What it doesn't do. |
Full flag reference: docs/commands.md.
zhao <name> falls through to a zhao-<name> binary on PATH when <name> isn't one of the
built-ins above — the same convention git uses for git <custom-command>. zhao-cli has no
compiled-in knowledge of any specific Addon; it only knows the naming convention and forwards
arguments, exit code, and output verbatim.
zhao-dbt-plan (a dbt microbatch cascading
time-window planner, AGPLv3, separate repo) is the first real Addon. See
examples/hello-zhao-addon/ for a minimal reference
implementation of the Addon contract if you want to build your own — its README.md is a
walkthrough of the whole discovery/input/output contract.
zhao lineage exports an interactive, self-contained lineage graph by default — click a model
or column to trace exactly what it depends on and what depends on it, search, filter, all
offline in one HTML file.
Open the live demo to try it yourself (rendered via htmlpreview.github.io, since GitHub shows raw HTML as source rather than rendering it — the file itself is also there to download and open locally). More in docs/lineage-html.md.
An optional zhao.yml at your project root lets your team set its own severity policy —
versioned in the repo, not hidden in a CI script:
preset: strict
rules:
column-added: pass
defer:
target: prod
state: artifacts/prod/manifest.jsonFull reference, including monorepo cascading: docs/configuration.md.
- uses: actions/checkout@v7
with: { fetch-depth: 0 } # zhao's Baseline resolution needs full history
- run: dbt compile
- run: curl -fsSL https://raw.githubusercontent.com/allenhori/zhao-cli/master/scripts/install.sh | sh
- run: PATH="$HOME/.zhao/bin:$PATH" zhao check --against origin/${{ github.base_ref }}Full example and notes: docs/ci-integration.md.
- Getting started
- Command reference
- Configuring
zhao.yml - CI integration
- Understanding lineage
- Architecture — how the code is organized, for contributors
- Releasing — how the stable/nightly channels work, for maintainers
zhao never connects to, stores, or holds credentials for your warehouse or database. zhao check/zhao diff/zhao lineage read only the compiled manifest.json dbt itself already
produced, entirely on your own machine or CI runner — no secret or token ever passes through
zhao to get there. The one place a live connection is genuinely useful (--check-relations,
fully optional) still doesn't change that: zhao never opens the connection itself, it hands
the check to dbt run-operation and borrows whatever connection your own dbt profile already
has.
zhao never reads, collects, or stores your actual data — no row values, nothing about what's
in your tables. It only derives structural metadata (schema, lineage, what changed), and
that metadata stays on your own filesystem, under target/zhao/, unless you decide otherwise.
Nothing is ever sent anywhere automatically: zhao-cli itself makes no network call of its
own except zhao update, which only downloads a release binary — it doesn't send anything
from your project. The one exception worth naming plainly: resolving a git-native Baseline
(the default zhao check --against <ref>, without --state) runs dbt compile/dbt deps as
a subprocess — the same commands you'd run yourself, and exactly as network-dependent as they
already are for you (package downloads, and often a live warehouse connection depending on
your adapter). Pass --state with an already-compiled manifest to skip that path entirely.
Nothing about your project is ever sent to zhao or any third party as a side effect of
check/diff/lineage — the metadata they write stays under target/zhao/, yours to do
whatever you want with, including nothing at all.
zhao also never generates or applies schema-evolution DDL for you: it detects that a change needs manual evolution or a backfill; the decision and the mechanism stay entirely yours.
Early, real-world usable. dbt is the first supported project format, not the definition of
what zhao is — the core engine (zhao-core) has no dbt-specific vocabulary baked in, so a
second Transformation Tool Adapter is a matter of implementing a trait, not rewriting the
engine.
cargo build --workspace
cargo test --workspaceRequires a recent stable Rust toolchain (edition 2024). Before opening a PR, also run:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warningsSee ARCHITECTURE.md for how the code is organized.
