# Reported issues for dbt-plan

Pod holds 10 of 10 GitHub reports that passed its relevance review. This can include external user reports, maintainer-confirmed bugs, and concrete feature gaps. Treat them as evidence to inspect, not a count of distinct defects.

Back to [dbt-plan](/mcp/dbt-plan).

## Most discussed

### dbt-plan run --format json prints a non-JSON line before the JSON

```
$ dbt-plan run --format json
Snapshot saved to .dbt-plan/base
{
  "summary": {
...
```

The first line is not JSON. `_do_snapshot` prints "Snapshot saved to ..." to stdout
(`cli.py:234`) on the way to `_do_check` printing the report (`cli.py:1198`), so
`json.loads` on `run`'s stdout fails.

`check --format json` is clean — the difference is that `run` calls `snapshot` first.

This matters because `docs/llms.txt:64-66` lists `run` and `check` side by side as the two
commands an agent can…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/165) · 2026-09-06 · open · 1 comment

### The MCP server and the Action cannot reach --target-dir, and the MCP has no way to make a baseline

`dbt-plan check` takes `--target-dir`. Neither wrapper passes it:

- MCP `plan` and `snapshot` take `project_dir` and nothing else (`server.py:66-70`, `145`)
- `action.yml` never passes it either (`:109`, `:118`, `:120`)

A project with `target-path: build` in `dbt_project.yml`, or `DBT_TARGET_PATH` set, gets
`No compiled SQL found` — which the MCP turns into `verdict: error` and the Action turns
into a failed step under `set -eu`. Both are loud rather than falsely safe, and both are
avoidable:…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/148) · 2026-09-06 · open · 1 comment

### Non-UTF-8 compiled SQL crashes with a traceback and exit 1, which the Action reports as destructive

`diff.py` handles non-UTF-8 compiled SQL on purpose:

```python
except UnicodeDecodeError:
    # Non-UTF-8 file: treat as modified with no cached SQL.
    # Callers will see base_sql=None / current_sql=None and
    # produce REVIEW REQUIRED — consistent with the false-safe-ban rule.
    diffs.append(ModelDiff(name, "modified", base_path, current_path))
    continue
```

`cli.py:990` responds to that `None` by reading the same file again, unguarded:

```python
if diff.current_sql is not None:…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/173) · 2026-09-06 · open · 0 comments

### The workflow ci-setup generates fails on warnings and never reaches its own Gate step, and the committed example is a different file

`dbt-plan ci-setup` writes `_CI_WORKFLOW` (`cli.py`). Its last two steps:

```yaml
      - name: Check current
        run: |
          git checkout ${{ github.event.pull_request.head.sha }}
          dbt compile
          dbt-plan check --format github >> $GITHUB_STEP_SUMMARY

      - name: Gate
        run: dbt-plan check
```

GitHub runs a `run:` block as `bash -e {0}`. `dbt-plan check` exits 2 on any warning, so on
a warning:

- **"Check current" fails**, and the job is red
- **"Gate" never…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/166) · 2026-09-06 · open · 0 comments

### The MCP server calls an acknowledged DROP COLUMN safe, and never passes on a stale target

#135 is one of three ways the MCP server's `verdict` disagrees with the report it was
built from. The other two are not fixed by deriving the verdict from `refusals`, because in
both of them `refusals` is empty.

Measured, dbt-core 1.11.7 + dbt-duckdb 1.10.1, an incremental with `sync_all_columns`
dropping `tax`.

### 1. An acknowledged drop becomes `safe` with nothing in `refusals`

```yaml
# .dbt-plan.yml
acknowledge_models:
  - fct
```

```
$ dbt-plan check
DESTRUCTIVE  fct (incremental,…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/162) · 2026-09-06 · open · 0 comments

### A partial or empty baseline reports the whole project SAFE, because every model looks newly added

A baseline missing compiled SQL is not detected. Every model it lacks is treated as newly
added, and an added model is `SAFE` by rule (`predictor.py:237-238`).

Measured on dbt-core 1.11.7 + dbt-duckdb 1.10.1. `src` drops `tax`; `reader` selects it:

```
baseline: .dbt-plan/base/{manifest.json, compiled/models/src.sql, compiled/models/reader.sql}

$ dbt-plan check                          # intact
DESTRUCTIVE  src (view)
  >> BROKEN_REF  reader: reads dropped column(s): tax
exit=1

$ rm…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/158) · 2026-09-06 · open · 0 comments

### A unit test whose given input is a versioned model is never checked, because two names for one model never meet

#43 taught cascade to check unit-test fixtures against dropped columns. It misses whenever
the fixture's `given` input is a versioned model.

Measured on dbt-core 1.11.7 + dbt-duckdb 1.10.1:

```yaml
models:
  - name: stg_orders
    latest_version: 2
    versions: [{v: 2}]
unit_tests:
  - name: t_fct
    model: fct
    given:
      - input: ref('stg_orders', v=2)
        rows: [{id: 1, amount: 2, tax: 3}]
```

`stg_orders_v2` drops `tax`. The fixture still supplies it.

```
$ dbt-plan check…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/157) · 2026-09-06 · open · 0 comments

### A corrupt or missing base manifest silently turns off deleted-model detection, and exit 0 comes back

`check` reads the base manifest best-effort. When it cannot be read, `base_node_index`
becomes `{}` and everything that depends on it turns off — including the deleted-model
detection #129 added — with nothing printed, not even under `-v`.

Measured on dbt-core 1.11.7 + dbt-duckdb 1.10.1. A model deleted from the project, its
orphan still in `target/compiled/` as #127 describes:

```
$ dbt-plan check                         # base manifest intact
DESTRUCTIVE  doomed (view)
  MODEL REMOVED…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/155) · 2026-09-06 · open · 0 comments

## Most recent

### One unnamed projection discards every column name in the model, and since #131 that means exit 2

`extract_columns` returns `None` for the whole model when any single projection has no
name (`columns.py:225-228`). Measured:

```
aliased everything         -> ['id', 'total']
unaliased count(*)         -> None      <-- whole model unreadable
unaliased coalesce         -> None
unaliased case             -> None
unaliased arithmetic       -> None
unaliased lower()          -> None
unaliased window fn        -> None
unaliased column ref       -> ['id', 'amount']
qualified unaliased col    ->…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/154) · 2026-09-06 · open · 0 comments

### The MCP server answers safe when warning_exit_code is 0, including for refusals

The MCP server's module docstring sets the rule it is built around:

> If this ever answers `{"safe": true}` for something dbt-plan actually declined to
> judge, it is worse than not existing.

It can, with one line of config.

`server.py:47` maps the exit code straight to a verdict:

```python
_VERDICTS = {0: "safe", 1: "destructive", 2: "review_required"}
```

`.dbt-plan.yml` can set what that 2 is:

```yaml
warning_exit_code: 0
```

`_exit_code_for` (`cli.py:455`) then returns 0 for every…

[Read the thread](https://github.com/PresentJay/dbt-plan/issues/135) · 2026-09-05 · open · 0 comments

The remaining reports are on [the project's issue tracker](https://github.com/PresentJay/dbt-plan/issues).
