Skip to main content
ClaudeWave

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.

ToolsOfficial Registry2 stars14 forksPythonApache-2.0Updated today
ClaudeWave Trust Score
95/100
Verified
Passed
  • Open-source license (Apache-2.0)
  • Actively maintained (<30d)
  • Clear description
  • Topics declared
  • Documented (README)
Last scanned: 9/8/2026
Get started
Method: Clone
Terminal
git clone https://github.com/PresentJay/dbt-plan
1. Clone the repository.
2. Follow the README for installation and usage instructions.
Use cases

Tools overview

# 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 
analytics-engineeringbigquerybreaking-changescici-cddata-contractsdata-engineeringdata-qualitydatabricksdbtdbt-coredeveloper-toolsduckdbgithub-actionspostgresredshiftschema-migrationsnowflakesqlglotstatic-analysis

What people ask about dbt-plan

What is PresentJay/dbt-plan?

+

PresentJay/dbt-plan is tools for the Claude AI ecosystem. 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. It has 2 GitHub stars and its last recorded update is dated 2026-09-08.

How do I install dbt-plan?

+

You can install dbt-plan by cloning the repository (https://github.com/PresentJay/dbt-plan) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is PresentJay/dbt-plan safe to use?

+

Our security agent has analyzed PresentJay/dbt-plan and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.

Who maintains PresentJay/dbt-plan?

+

PresentJay/dbt-plan is maintained by PresentJay. The last recorded GitHub activity is dated 2026-09-08, with 61 open issues.

Are there alternatives to dbt-plan?

+

Yes. On ClaudeWave you can browse similar tools at /categories/tools, sorted by popularity or recent activity.

Deploy dbt-plan to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: PresentJay/dbt-plan
[![Featured on ClaudeWave](https://claudewave.com/api/badge/presentjay-dbt-plan)](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>

More Tools

dbt-plan alternatives