dbt-runner
Invocation discipline and failure triage for running dbt against a warehouse.
Half of all "dbt is broken" sessions are environment problems detectable
before the first command, and most of the rest are misread output. This
skill front-loads the former and catalogues the latter.
First action, every invocation
Check for .dbt-runner/context.md in the dbt project root.
- Absent → first run. Read references/install.md
and run the bootstrap (discovers profile, target, engine, required env
vars → writes the context file). Then continue.
- Present → load it; it is the source of truth for this project's dbt
setup (profile, target, engine, required env-var names, project lore).
The context file holds names only, never secrets: no env-var values,
no passphrases. It is committed to the project repo.
Preflight, once per session, before the first dbt command
python3 <skill-dir>/scripts/preflight.py --project-root <dbt-project-root>
Static checks only (sandbox-safe, no network): required env vars set, the
private key file exists, dbt_packages/ present with a clean
package-lock.yml, and the profile/target resolve in profiles.yml. One
line per check (OK/FAIL/SKIP), non-zero exit on any FAIL. Fix every
FAIL before running dbt; each line says how. Don't re-run it before every
command; once per session is the contract, plus once more after any
environment change (new shell, edited .env, switched target).
Invocation rules, every dbt command
- Invoke via the context file's
runner, never bare dbt. dbt-fusion
ships as dbt, so PATH often resolves to a different engine than the one
the project pins: a fusion binary on a core project hard-errors on
deprecations and never reaches SQL compilation, which reads as "the project
is broken". If the context file has no runner, derive it (uv.lock →
uv run dbt, poetry.lock → poetry run dbt, .venv/ →
.venv/bin/dbt) and record it. Before trusting a parse-stage failure,
confirm the engine with <runner> --version.
- Run outside the sandbox. dbt needs network access to the warehouse.
In a sandboxed shell it fails with DNS/connection errors that masquerade
as auth problems. If you see a connection error, suspect the sandbox
first, and do not start debugging credentials.
- Never pipe dbt output; redirect to a logfile. Piped output
(
dbt build | tail) can buffer and return blank. Always:<runner> build --select <sel> > /tmp/dbt_run.log 2>&1
then grep/tail the logfile. Exit code first, log second.
- Long runs go to the background. Full builds or large facts can
exceed default tool timeouts. Run in the background (or raise the
timeout) and poll the logfile. The context file's lore section lists
known-slow models.
- Verify the selection matched. After every run, confirm the log shows
a non-zero model count.
No models available / "Nothing to do" from a
typo'd --select exits 0 and looks like success while doing nothing.
- Check the run was not vacuous. A model whose spine is empty in the
local environment builds 0 rows, and every data test on it passes
trivially.
PASS=n ERROR=0 on an empty model proves it compiles, nothing
more. When a model's source is populated outside dbt's DAG (ML scores,
externally-loaded tables), count the rows before reporting success, and
exercise the logic on synthetic rows via <runner> show --inline.
Reading the result
dbt build exiting non-zero does not mean models failed to build.
Read the summary line, PASS=… WARN=… ERROR=…, and distinguish three
outcomes before reacting (this trichotomy is the authoritative reference;
failures.md points here):
- Run error. A model errored; its SQL or upstream is broken.
- Test failure. Models built fine, a data test failed. Fix data or
test, don't touch the build invocation.
- Warning.
severity: warn tests print WARN and do not fail the
run. Don't "fix" a warning as if it were a failure, and don't report a
warned run as broken.
When something fails
Fix the failure in front of you, at the scope it demands. Model refactors,
test additions, and cleanup the failure did not require get reported, not
performed. Work the escalation ladder below inline: it is greps and file
lookups, not subagent work.
Escalation ladder, in order, no skipping:
- Grep the logfile for the error signature and look it up in
references/failures.md. Entries are keyed by
the verbatim string, with causes ranked by prior.
- If the signature looks like connection/auth and the static preflight
passes, run the live check:
python3 <skill-dir>/scripts/preflight.py --connect (outside the
sandbox).
- If the context file says
engine: fusion, also check
references/fusion.md. Fusion has failure modes
with misleading error messages (unit-test fixture inference, blocked
sibling tests).
Project-specific lore (seed/test couplings, known-slow models, schema
quirks) accumulates in the context file's Project lore section. Append
a line or two when you learn something the hard way, not a writeup.
While a build runs, speak up only on a finding or a change of direction.
Report back leading with the outcome, what the run did or what the root
cause was, then the supporting detail.
1---2name: dbt-runner3description: Use when running any dbt command (build, run, test, compile, seed, deps) or debugging a dbt failure, including connection and auth errors, parse errors, hanging or silently-empty runs, and dbt-fusion quirks. Enforces preflight checks and output-capture discipline before the first dbt invocation of a session, and maps error signatures to causes and fixes. Bootstraps a per-project .dbt-runner/context.md on first use.4---56# dbt-runner78Invocation discipline and failure triage for running dbt against a warehouse.9Half of all "dbt is broken" sessions are environment problems detectable10before the first command, and most of the rest are misread output. This11skill front-loads the former and catalogues the latter.1213## First action, every invocation1415Check for **`.dbt-runner/context.md`** in the dbt project root.1617- **Absent** → first run. Read [references/install.md](references/install.md)18 and run the bootstrap (discovers profile, target, engine, required env19 vars → writes the context file). Then continue.20- **Present** → load it; it is the source of truth for this project's dbt21 setup (profile, target, engine, required env-var *names*, project lore).2223The context file holds **names only, never secrets**: no env-var values,24no passphrases. It is committed to the project repo.2526## Preflight, once per session, before the first dbt command2728```bash29python3 <skill-dir>/scripts/preflight.py --project-root <dbt-project-root>30```3132Static checks only (sandbox-safe, no network): required env vars set, the33private key file exists, `dbt_packages/` present with a clean34`package-lock.yml`, and the profile/target resolve in `profiles.yml`. One35line per check (`OK`/`FAIL`/`SKIP`), non-zero exit on any `FAIL`. Fix every36`FAIL` before running dbt; each line says how. Don't re-run it before every37command; once per session is the contract, plus once more after any38environment change (new shell, edited `.env`, switched target).3940## Invocation rules, every dbt command41421. **Invoke via the context file's `runner`, never bare `dbt`.** dbt-fusion43 ships as `dbt`, so PATH often resolves to a different engine than the one44 the project pins: a fusion binary on a core project hard-errors on45 deprecations and never reaches SQL compilation, which reads as "the project46 is broken". If the context file has no `runner`, derive it (`uv.lock` →47 `uv run dbt`, `poetry.lock` → `poetry run dbt`, `.venv/` →48 `.venv/bin/dbt`) and record it. Before trusting a parse-stage failure,49 confirm the engine with `<runner> --version`.502. **Run outside the sandbox.** dbt needs network access to the warehouse.51 In a sandboxed shell it fails with DNS/connection errors that masquerade52 as auth problems. If you see a connection error, suspect the sandbox53 *first*, and do not start debugging credentials.543. **Never pipe dbt output; redirect to a logfile.** Piped output55 (`dbt build | tail`) can buffer and return blank. Always:56 ```bash57 <runner> build --select <sel> > /tmp/dbt_run.log 2>&158 ```59 then grep/tail the logfile. Exit code first, log second.604. **Long runs go to the background.** Full builds or large facts can61 exceed default tool timeouts. Run in the background (or raise the62 timeout) and poll the logfile. The context file's lore section lists63 known-slow models.645. **Verify the selection matched.** After every run, confirm the log shows65 a non-zero model count. `No models available` / "Nothing to do" from a66 typo'd `--select` exits 0 and looks like success while doing nothing.676. **Check the run was not vacuous.** A model whose spine is empty in the68 local environment builds 0 rows, and every data test on it passes69 trivially. `PASS=n ERROR=0` on an empty model proves it compiles, nothing70 more. When a model's source is populated outside dbt's DAG (ML scores,71 externally-loaded tables), count the rows before reporting success, and72 exercise the logic on synthetic rows via `<runner> show --inline`.7374## Reading the result7576`dbt build` exiting non-zero does **not** mean models failed to build.77Read the summary line, `PASS=… WARN=… ERROR=…`, and distinguish three78outcomes before reacting (this trichotomy is the authoritative reference;79failures.md points here):8081- **Run error.** A model errored; its SQL or upstream is broken.82- **Test failure.** Models built fine, a data test failed. Fix data or83 test, don't touch the build invocation.84- **Warning.** `severity: warn` tests print WARN and do *not* fail the85 run. Don't "fix" a warning as if it were a failure, and don't report a86 warned run as broken.8788## When something fails8990Fix the failure in front of you, at the scope it demands. Model refactors,91test additions, and cleanup the failure did not require get reported, not92performed. Work the escalation ladder below inline: it is greps and file93lookups, not subagent work.9495Escalation ladder, in order, no skipping:96971. Grep the logfile for the error signature and look it up in98 [references/failures.md](references/failures.md). Entries are keyed by99 the verbatim string, with causes ranked by prior.1002. If the signature looks like connection/auth and the static preflight101 passes, run the live check:102 `python3 <skill-dir>/scripts/preflight.py --connect` (outside the103 sandbox).1043. If the context file says `engine: fusion`, also check105 [references/fusion.md](references/fusion.md). Fusion has failure modes106 with misleading error messages (unit-test fixture inference, blocked107 sibling tests).108109Project-specific lore (seed/test couplings, known-slow models, schema110quirks) accumulates in the context file's **Project lore** section. Append111a line or two when you learn something the hard way, not a writeup.112113While a build runs, speak up only on a finding or a change of direction.114Report back leading with the outcome, what the run did or what the root115cause was, then the supporting detail.