Catch breaking dbt schema changes before dbt run executes them: dropped columns, the downstream models and data tests they break, contract violations. Static analysis of compiled SQL, no warehouse connection. Like terraform plan, for dbt.
- ✓Open-source license (Apache-2.0)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
git clone https://github.com/PresentJay/dbt-planResumen de Tools
# dbt-plan
Static analysis tool that warns about risky DDL changes before `dbt run`.
Like `terraform plan` for dbt, and used the same way: you run it **before** the thing
that changes your warehouse, not only in CI afterwards.
Runs on compiled SQL. It reads files and nothing else, so it works with any warehouse —
Snowflake, BigQuery, Redshift, Postgres, DuckDB — through one code path.
## What It Looks Like
```
$ dbt-plan check
dbt-plan -- 2 model(s) changed
DESTRUCTIVE int_order_enriched (incremental, sync_all_columns)
DROP COLUMN shipping_info
DROP COLUMN billing_info
ADD COLUMN shipping_city
Downstream: dim_customers, fct_orders (2 model(s))
>> BROKEN_REF fct_orders: references dropped column(s): shipping_info
SAFE dim_customers (table)
CREATE OR REPLACE TABLE
dbt-plan: 2 checked, 1 safe, 0 warning, 1 destructive, 1 cascade risk(s)
```
## What It Does
dbt-plan analyzes compiled SQL diffs to catch dangerous schema changes at PR time:
- **Column changes**: detects ADD/DROP COLUMN from SQL diff
- **Risk assessment**: judges safety based on materialization x on_schema_change rules
- **Cascade analysis**: finds downstream models broken by a dropped column — the ones that
name it, resolved against the project's own schema rather than matched as text; the ones
that select `*` and lose it without their own file changing; and the tests whose fixtures
pin it down. Names the exposures whose owners need telling
- **Contracts**: reports a change an enforced contract will reject, in either direction
- **Config changes**: detects materialization or on_schema_change policy changes
- **Type changes**: compares explicit `CAST` types between revisions
- **`SELECT *` resolution**: reads the columns from the CTEs of the same statement, and follows a `ref()` into the referenced model's compiled SQL
It does NOT execute anything, connect to any warehouse, or simulate `dbt run`. It reads files, compares them, and warns you.
## Quick Start
```bash
pip install dbt-plan
dbt-plan run # compile baseline → compile current → check
```
`dbt-plan run` does the whole thing in one command, and needs whatever credentials your
`dbt compile` normally needs.
### The loop it is built for
Once you have a baseline, the inner loop is a single sub-second command. Edit a model or a
macro, recompile, and see what `dbt run` would do — *before* running it:
```bash
dbt-plan snapshot # once, on the revision you are changing from
# ... edit models, edit macros ...
dbt compile && dbt-plan check
```
Measured on a project of 3 models, median of 3 runs:
| step | time |
|---|---|
| `dbt compile` (Fusion) | 1.8 – 3.8 s |
| **`dbt-plan check`** | **0.11 s** |
| `dbt-plan snapshot` | 0.10 s |
200 models, every one of them changed: **0.48 s**. The compile is the cost, and you were
compiling anyway — dbt-plan itself is fast enough to sit in the edit loop rather than at
the end of it.
### Working with a coding agent
An agent editing models cannot eyeball a diff and hesitate. Give it the check and the
reasons behind it:
```bash
dbt-plan agent-setup # writes dbt-plan guidance into your AGENTS.md
dbt-plan check --format json
```
The guidance leads with what an agent most often gets wrong: adding a model to
`ignore_models`, or downgrading `on_schema_change` from `sync_all_columns` to `ignore`,
silences a real finding without making the change safe.
Or give it the check as an MCP tool:
```bash
pip install 'dbt-plan[mcp]'
dbt-plan-mcp # stdio MCP server exposing `plan` and `snapshot`
```
`plan` returns the verdict, the per-model operations, and — separately — a `refusals`
list naming everything dbt-plan declined to judge. That separation is the point: a person
reading "safe" may still glance at the diff, an agent reading it proceeds, so a
non-empty `refusals` must never be collapsed into the verdict.
Both tools accept `target_dir` when dbt writes artifacts outside the default `target/`,
for example `plan(project_dir=".", target_dir="build")`.
The server is a separate package from the analysis core. The core is offline and
synchronous by design and `tests/test_invariants.py` fails the build on an `asyncio` or
network import anywhere inside it; an MCP server is both, so keeping them apart is what
keeps that guarantee provable.
Registry entry — the line below is how the MCP registry verifies that whoever publishes
the entry also owns this PyPI package, so it has to stay in the README that ships:
```
mcp-name: io.github.PresentJay/dbt-plan
```
### More commands
```bash
dbt-plan init # Generate .dbt-plan.yml config + update .gitignore
dbt-plan stats # Analyze project readiness
dbt-plan ci-setup # Generate GitHub Actions workflow
dbt-plan check --format github # GitHub markdown output
dbt-plan check --format json # JSON for CI pipelines
dbt-plan run --against main # compare with where this branch left main
dbt-plan check --select fct_orders # one model
dbt-plan check --select fct_orders+ # it and everything downstream
```
## Scope
dbt-plan is a **static analysis warning tool**, not a runtime simulator.
| In scope | Out of scope |
|----------|-------------|
| Column ADD/DROP detection from compiled SQL | `dbt run` simulation |
| materialization × on_schema_change risk rules | Warehouse connection |
| Cascade: broken refs, build failures, inherited column loss | `seed` / `source` change detection |
| Config change detection (materialization, osc) | `pre_hook` / `post_hook` DDL analysis |
| Unit test fixtures and exposure owners downstream | `seed` / `source` fixtures dbt-plan cannot read |
| Enforced-contract violations: names, and types by family | Contract types compared more finely than family |
| Explicit `CAST` type changes | Type changes on uncast columns |
| `SELECT *` resolved through CTEs and `ref()` | `SELECT *` over a source or a raw table |
| CI exit codes + structured output | `full_refresh` mode judgment |
**Design principle**: false warnings are OK, false safe is never OK.
## When to use it
dbt-plan answers a narrower question than the warehouse-connected tools (Recce,
SQLMesh, data-diff) and costs nothing to run, so it works as the cheap gate in
front of them — and on the Fusion engine, which compiles without a warehouse
connection, that includes fork pull requests where they cannot run at all.
See [use cases](docs/use-cases.md) for the comparison, real timings, and what it
gets wrong.
## Deliberately Not Planned
Ideas that look useful but contradict what this tool is:
| Idea | Why not |
|------|---------|
| INFORMATION_SCHEMA query | Requires a warehouse connection. dbt-plan reads files and nothing else, which is what lets it run wherever its input exists — including a fork's pull request, once the project compiles on Fusion. |
| Type changes on columns with no explicit `CAST` | The type is whatever the warehouse assigned, so seeing a change would mean asking it. Columns that *are* cast explicitly on both sides are compared — see below. |
## DDL Prediction Rules
| Materialization | on_schema_change | Predicted DDL | Safety |
|-----------------|------------------|---------------|--------|
| table | any | `CREATE OR REPLACE TABLE` | SAFE |
| view | any | `CREATE OR REPLACE VIEW` | SAFE |
| ephemeral | any | (no physical object) | SAFE |
| snapshot | any | `REVIEW REQUIRED` | WARNING |
| incremental | ignore | no DDL | SAFE |
| incremental | fail | build failure | WARNING |
| incremental | append_new_columns | `ADD COLUMN` only | SAFE |
| incremental | sync_all_columns | `ADD + DROP COLUMN` | DESTRUCTIVE if columns removed |
| any | (model removed) | `MODEL REMOVED` | DESTRUCTIVE |
| any | (unknown osc) | `UNKNOWN on_schema_change` | WARNING |
| materialized_view / custom | (not set by you) | `UNKNOWN materialization` | WARNING |
| materialized_view / custom | (you set one) | follows the incremental rules | per osc |
| any | (`contract: {enforced: true}`) | `CONTRACT VIOLATION` | WARNING |
Under a contract, a column's declared `data_type` is compared with its explicit `CAST`,
by family -- text against number against date/time against boolean. `varchar` and `text`
are the same family and not a finding; `varchar` and `integer` are a build failure.
Comparing more finely means a per-adapter type table, and a wrong answer about a type is
worse than no answer.
"Not set by you" means the author wrote no `on_schema_change`, in the model or in
`dbt_project.yml`. dbt resolves one for every model regardless, so the resolved value
asserts nothing; an explicit setting is a claim about how that materialization behaves
and is honoured. dbt-plan reads `unrendered_config` to tell them apart.
An enforced contract inverts the rules above: dbt requires every column to be declared,
so a column added to the SQL fails the build just as a removed one does. Names only —
dbt compares its declared `data_type` against the warehouse, which dbt-plan does not read.
## CI Integration (GitHub Actions)
```yaml
name: dbt-plan
on:
pull_request:
jobs:
plan:
runs-on: ubuntu-latest
permissions:
contents: read
env:
# Whatever your profiles.yml reads. `dbt compile` connects; dbt-plan does not.
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
SNOWFLAKE_PRIVATE_KEY: ${{ secrets.SNOWFLAKE_PRIVATE_KEY }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the base revision has to be in the clone
persist-credentials: false
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install uv && uv sync
- uses: PresentJay/dbt-plan@v1
```
Keep the `pull_request` trigger. Never switch it to `pull_request_target` — `dbt compile`
runs Jinja and macros written in the pull request, so that would hand your warehouse
credentials to Lo que la gente pregunta sobre dbt-plan
¿Qué es PresentJay/dbt-plan?
+
PresentJay/dbt-plan es tools para el ecosistema de Claude AI. Catch breaking dbt schema changes before dbt run executes them: dropped columns, the downstream models and data tests they break, contract violations. Static analysis of compiled SQL, no warehouse connection. Like terraform plan, for dbt. Tiene 2 estrellas en GitHub y su última actualización registrada es del 2026-09-08.
¿Cómo se instala dbt-plan?
+
Puedes instalar dbt-plan clonando el repositorio (https://github.com/PresentJay/dbt-plan) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar PresentJay/dbt-plan?
+
Nuestro agente de seguridad ha analizado PresentJay/dbt-plan y le ha asignado un Trust Score de 95/100 (tier: Verified). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene PresentJay/dbt-plan?
+
PresentJay/dbt-plan es mantenido por PresentJay. La última actividad registrada en GitHub es del 2026-09-08, con 61 issues abiertos.
¿Hay alternativas a dbt-plan?
+
Sí. En ClaudeWave puedes explorar tools similares en /categories/tools, ordenados por popularidad o actividad reciente.
Despliega dbt-plan en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
[](https://claudewave.com/repo/presentjay-dbt-plan)<a href="https://claudewave.com/repo/presentjay-dbt-plan"><img src="https://claudewave.com/api/badge/presentjay-dbt-plan" alt="Featured on ClaudeWave: PresentJay/dbt-plan" width="320" height="64" /></a>Más Tools
A single CLAUDE.md file to improve Claude Code behavior, derived from Andrej Karpathy's observations on LLM coding pitfalls.
An AI skill that provides design intelligence for building professional UI/UX across multiple platforms.
🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman
CLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies
The fastest, litest AI Gateway. Rust core with Python SDK. Call 100+ LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logging [Bedrock, Azure, OpenAI, Anthropic, OpenAI, VertexAI, vLLM, Nvidia NIM]
Use Claude Code, Codex, Pi, and OpenCode and more for free (1.3B+ free tokens) from your terminal, app, IDE, or phone like OpenClaw (voice supported + ToS friendly)