DVT LS, SHOW & DEBUG
Three verbs for looking before you leap: dvt ls tells you what a selection would match, dvt show previews the rows a model would produce, and dvt debug proves your connections actually work. If you know these verbs from dbt, you already know how to drive them — same flags, same selection syntax, same behavior — and none of the three ever materializes or changes a thing.
$ dvt ls --select orders+ analytics.marts.orders analytics.marts.fct_order_margins analytics.not_null_orders_order_id analytics.unique_orders_order_id
That's the spirit of the whole page: ask first, spend later. Each verb gets its own section below, and the full flag tables are at the end.
DVT LS — RESOLVE THE SELECTION, SPEND NOTHING
dvt ls resolves your selection exactly as a run would — same selector engine, same graph — then prints what it matched instead of executing it. It is the safe way to check a --select expression before you spend a warehouse on it: the four nodes above are precisely what dvt run --select orders+ would build and dvt build would test.
dvt listis the same command, letter for letter. And because the graph is the project's whole graph, everything shows up as a node — models, tests, seeds, snapshots, and your sources on every connection. A federated model that reads Postgres and Snowflake in one query is just another line here; selection reaches across engines the same way run does.
--output changes what each matched node prints as. path gives you the file on disk — handy for piping into an editor:
$ dvt ls --select orders+ --resource-type model --output path models/marts/orders.sql models/marts/fct_order_margins.sql
And json gives you one object per line, with --output-keys choosing which fields ride along — made for jq and CI scripts:
$ dvt ls --resource-type model --output json --output-keys "name resource_type"
{"name": "stg_orders", "resource_type": "model"}
{"name": "stg_payments", "resource_type": "model"}
{"name": "orders", "resource_type": "model"}
{"name": "fct_order_margins", "resource_type": "model"}One DVT-ism worth knowing: like every graph verb, ls begins from a passing dvt parse. A project that doesn't parse answers with the parse error itself — named file, named line — never a selector stack trace on top of a broken manifest.
DVT SHOW — PREVIEW ROWS, MATERIALIZE NOTHING
dvt show compiles a model, runs it, and prints a preview of the rows that came back. Nothing lands in the warehouse — this is a look, not a build. Five rows by default:
$ dvt show --select orders 14:07:31 Previewing node 'orders': | order_id | customer_id | order_date | status | | -------- | ----------- | ---------- | ------- | | 9110 | 214 | 2026-07-30 | queued | | 9111 | 58 | 2026-07-30 | shipped | | 9112 | 214 | 2026-07-31 | shipped | | 9113 | 17 | 2026-08-01 | shipped | | 9114 | 42 | 2026-08-01 | queued |
--limit raises or lowers the row count (-1 means no limit at all), and the limit rides inside the query itself, so previewing a model over a billion-row table stays cheap:
$ dvt show --select orders --limit 20
The other way in is --inline — an ad-hoc query instead of a file, with ref() and source()fully available. It's the quickest possible answer to "what's actually in this model right now?":
$ dvt show --inline "select max(order_date) as latest_order from {{ ref('orders') }}"
14:09:02 Previewing inline node:
| latest_order |
| ------------ |
| 2026-08-01 |--output json switches the preview from the table to JSON for scripts, and -f / --full-refreshpreviews an incremental model as the full rebuild it would be — again without touching what's materialized.
One boundary to know: show looks at your project through the default target, project semantics included. When you want raw, engine-native SQL on any named connection — no Jinja, no project, just the engine — dvt exec is the sibling built for exactly that.
DVT DEBUG — PROVE THE WIRING
dvt debug starts exactly where you expect: the checks you know, against the default target (or --target). Then DVT adds one more line — the same connection tested through Sling, the engine that moves data between systems. DVT moves your data through both layers, so debug proves both:
$ dvt debug profiles.yml file [OK found and valid] dbt_project.yml file [OK found and valid] Required dependencies: - git [OK found] Connection: host: prod-pg.internal port: 5432 user: analytics database: prod schema: analytics Connection test: [OK connection ok] All checks passed! Sling (data movement) [pg_prod]: OK connection ok
A connection can pass the adapter handshake and still fail bulk data movement (or the reverse) — a firewall rule, a driver, a credential scoped to one protocol. Catching that here costs seconds; catching it mid-run costs the run.
The real upgrade is --all: every output in your profile, both layers, in parallel, one screen. Buckets show n/a on the adapter column — they hold files (csv, parquet, json, jsonl), so Sling is their only layer:
$ dvt debug --all Testing all connections for profile 'analytics' (adapter + Sling) ... ✅ duckdb_local ✨ Adapter: OK ⚡️ Sling: OK duckdb ✅ pg_prod ✨ Adapter: OK ⚡️ Sling: OK postgres ✅ s3_landing ✨ Adapter: n/a ⚡️ Sling: OK s3 ❌ sf_finance ✨ Adapter: FAIL ⚡️ Sling: FAIL snowflake ↳ 250001 (08001): incorrect username or password was specified 3/4 connections fully operational
Failures come with the reason, not boilerplate — the engine's own last meaningful line, like the Snowflake auth error above. A connection whose adapter simply isn't installed says so and names the fix: adapter not installed — run `dvt sync`.
IN PLAIN DBT
dbt debug tests exactly one connection: the active target. Every other output in profiles.yml lives on faith until a run dies against it. A federated project lives across connections — so DVT tests the whole profile at once, every output on both layers, and tells you 3/4instead of letting number four ambush Monday's run.
dvt diagnose is the same full matrix, kept as a spelling — it says so and runs it:
$ dvt diagnose `dvt diagnose` is an alias of `dvt debug --all` Testing all connections for profile 'analytics' (adapter + Sling) ...
When the profile includes engines with a native bulk-load lane, debug also states whether that lane is available on thismachine — with the consequence spelled out, not implied. Oracle's sqlldr, for example:
Engine preflight: oracle: sqlldr found — bulk load lane available
If the tool is missing, the row says what that costs (Oracle loads fall back to a much slower cursor lane) and where the tool comes from — so "why is this load slow?" gets answered before the load, not after.
Two last habits worth building on. Debug is deliberately never parse-gated — it is one of the tools you fix a broken project with, so it answers even when nothing else will. And the exit code is honest: 0 only when every tested connection is fully operational, 1 otherwise — which makes dvt debug --all a one-line health gate for CI.
REFERENCE — DVT LS FLAGS
| FLAG | TYPE | DEFAULT | WHAT IT DOES |
|---|---|---|---|
| -s, -m, --select, --models, --model | tuple | everything | The nodes to include — the same selection syntax run uses, resolved by the same selector engine. Graph operators (+, @), tag:, source:, path selectors: all of it. |
| --exclude | tuple | — | The nodes to leave out of the resolved selection. |
| --selector | string | — | A named selector from selectors.yml instead of an inline expression. |
| --resource-types, --resource-type | type | all types | Restrict the listing to these resource types (model, test, seed, snapshot, source, ...). --exclude-resource-types is the mirror. |
| --output | [name|path|selector|json] | selector | How each matched resource prints: bare name, file path, dotted selector form, or one JSON object per line. |
| --output-keys | tuple | all keys | With --output json, which keys each object carries — e.g. 'name resource_type' for lean script input. |
The engine-wide options — project and target, state and deferral, logging — apply here exactly as they do on run; dvt ls --help prints the complete set.
REFERENCE — DVT SHOW FLAGS
| FLAG | TYPE | DEFAULT | WHAT IT DOES |
|---|---|---|---|
| -s, --select | tuple | — | The model (or models) to preview. Same selection syntax as everywhere else. |
| --inline | string | — | Preview the results of this SQL instead of a file. ref() and source() work inside it. |
| --limit | integer | 5 | How many rows to preview. -1 removes the limit entirely. The limit is applied inside the query, so big models stay cheap to peek at. |
| --output | [json|text] | text | The preview as a readable table, or as JSON for scripts. |
| --introspect / --no-introspect | flag pair | introspect | Whether to run introspective queries while compiling the preview. |
| -f, --full-refresh | flag | off | Preview an incremental model as the full rebuild it would be — without materializing anything. |
The same engine-wide options apply; dvt show --help has the full list.
REFERENCE — DVT DEBUG FLAGS
| FLAG | TYPE | DEFAULT | WHAT IT DOES |
|---|---|---|---|
| --all | flag | off | Test every output in the profile, both layers (adapter + Sling), in parallel. dvt diagnose is an alias for exactly this. |
| --target, -t | string | profile's default | Which single output to test on the parity path. Without --all, debug tests this one connection on both layers. |
| --project | string | from dbt_project.yml | The profile name to test. Normally inferred from the project; pass it explicitly when running outside one. |
| --project-dir | path | . | The project directory to read dbt_project.yml from. |
| --profiles-dir | path | ~/.dbt | Where to find profiles.yml, when it isn't in the standard place. |
On the single-target path, any extra flags you pass ride through to the underlying connection check untouched — debug never eats a flag it doesn't know.