What you will be able to do
- Name the error type dbt reports for each stage of a run, and say what each type means about where the fault is
- Read the file location in an error message and go straight to the file that caused it
- Tell a dbt-side Compilation Error apart from a Database Error that the warehouse sends back
- Find what really happened in logs/dbt.log, and tune what goes to the terminal and the log file with log-level, debug and quiet flags
- Read dbt's exit codes 0, 1 and 2 in scripts and CI
Key concept
Error type as a pointer to the failing stage — Each dbt error names its type and the file it came from. The type tells you which stage of the run failed: Runtime for initialization, Compilation for parsing, Dependency for graph validation, Database for SQL execution. That tells you whether to look at your setup, your Jinja and YAML, your refs, or your SQL.
1.What a dbt error message tells you
A failed dbt command does not print a random stack trace. The error message almost always has two parts. The first is the error type, such as Runtime Error, Compilation Error or Database Error. The second is the file where the problem happened. Start debugging from those two facts: open the file it names, look for a quick fix, and only then widen the search.
The error type matters because dbt works in stages. Commands like dbt run first check that you're in a dbt project and can connect. Next dbt parses your Jinja and YAML, then builds and checks the dependency graph, and only after that sends SQL to the warehouse. Each stage raises its own type of error. So the type tells you how far dbt got before it stopped.
| Step | What dbt checks | Error type |
|---|---|---|
| Initialize | That this is a dbt project, and that dbt can connect to the warehouse | Runtime Error |
| Parsing | That Jinja snippets in .sql files are valid, and that .yml files are valid | Compilation Error |
| Graph validation | Compiles dependencies into a graph and checks that it is acyclic | Dependency Error |
| SQL execution | Runs the models | Database Error |
Here is a full failure summary from the terminal. The first line names the type and the model, with its path in brackets. The next line is the warehouse's own message, and the line after that says where the compiled SQL was written. The Done. line counts the outcomes. A single error here also means anything downstream of customers was skipped, which is why one broken model can turn a whole run red.
Completed with 1 error and 0 warnings:
Database Error in model customers (models/customers.sql)
Syntax error: Expected ")" but got identifier `your-info-12345` at [13:15]
compiled SQL at target/run/jaffle_shop/customers.sql
Done. PASS=0 WARN=0 ERROR=1 SKIP=0 TOTAL=1Checkpoint 1 of 9· Match them up
Match each error type to the stage of a dbt run that raises it
Tap a term, then the definition that fits it.
The error type follows the stages of a run in order, so it shows how far dbt got before failing.
“Compile the dependencies into a graph. Check that it's acyclic.”Source: docs.getdbt.com
Checkpoint 2 of 9· Exam question
A teammate's pull request fails during dbt's parsing step with this error: Compilation Error - Invalid models config given in models/staging/schema.yml Additional properties are not allowed ('materialize' was unexpected) models: - name: stg_orders materialize: table columns: - name: order_id data_tests: - unique Which line is causing this specific error, and why?
Correct answer: A — `materialize: table`, because the valid config key for choosing a model's materialization is `materialized`, and dbt rejects `materialize` as an unrecognized property.
- A. This is the actual typo: dbt's model properties schema only recognizes `materialized` as a config key for choosing table, view, incremental, or ephemeral. Because `materialize` is not a valid key in that schema, the parser rejects the whole properties file with the exact 'Additional properties are not allowed' message shown.
- B. This is incorrect because `data_tests:` is valid current syntax for declaring column tests and is not the source of an unrecognized-property error here. Renamed or deprecated keys typically surface a deprecation warning rather than this 'unexpected property' failure.
- C. This is incorrect because column-level tests and descriptions are meant to be nested directly under the model entry in the same properties file, not split into a separate file. There is no `column_properties.yml` convention in dbt, so this explanation misdescribes valid structure.
- D. This is incorrect because dbt resolves model names from the SQL filename during parsing, not from a case match against the compiled warehouse relation. A mismatch of this kind would surface as a missing-node or dependency error, not this properties-schema error.
2.Runtime and Dependency errors: before any SQL runs
A Runtime Error means dbt never got as far as your models. The cause is your environment, not your code. The clearest example is running dbt from the wrong folder:
Running with dbt=1.7.1
Encountered an error:
Runtime Error
fatal: Not a dbt project (or any of the parent directories). Missing dbt_project.yml fileThe fix is to use pwd and cd until you're in the folder that holds dbt_project.yml. Other Runtime Errors come from the connection setup. Could not find profile named 'jaffle_shops' means the profile: key in dbt_project.yml doesn't match any top-level name in profiles.yml. A single letter is enough: jaffle_shops against jaffle_shop. A bad password shows as a Runtime Error with a Database Error nested inside it, because the warehouse refused the connection while dbt was listing schemas. In both cases dbt debug --config-dir shows where profiles.yml lives. After a fix, dbt debug confirms that profiles.yml, dbt_project.yml and the connection test all pass.
A Dependency Error happens one stage later. dbt has parsed every file but can't order the models, because the refs form a loop. The message spells the loop out, as in Found a cycle: model.jaffle_shop.customers --> model.jaffle_shop.stg_customers --> model.jaffle_shop.customers. You fix it by changing the ref calls so the loop breaks. If a model needs to refer to itself, use {{ this }} rather than a ref to its own name. Laying out a DAG so this can't happen is covered in [building clean DAGs](lesson:1.5).
Checkpoint 3 of 9· Check yourself
dbt run fails with Runtime Error ... Could not find profile named 'jaffle_shops'. The models compiled fine yesterday on a colleague's machine. What do you check first?
A Runtime Error comes from initialization, before any model runs. This one says the profile named in dbt_project.yml isn't defined in your local profiles.yml.
“Check the profile: key in your dbt_project.yml.”Source: docs.getdbt.com
Sources1
3.Compilation Error or Database Error: is it dbt or the SQL?
The most useful thing an error type tells you is which side the fault is on: dbt's or the warehouse's. A Compilation Error is raised by dbt itself, before any SQL leaves your machine. A bad ref is one cause. Broken Jinja is another. In that case dbt hands on the message from the Jinja library, along with the file where it happened:
$ dbt run -s customers
Running with dbt=1.1.0
Encountered an error:
Compilation Error in model customers (models/customers.sql)
Model 'model.jaffle_shop.customers' (models/customers.sql) depends on a node named 'stg_customer' which was not found$ dbt run
Running with dbt=1.7.1
Compilation Error in macro (macros/cents_to_dollars.sql)
Reached EOF without finding a close tag for macro (searched from line 1)That EOF message comes from a missing {% endmacro %}. The same kind of error appears if you forget a closing }, or close a for loop before the if inside it. Invalid YAML and unknown keys in properties files are also Compilation Errors, because dbt reads .yml files during parsing. Reading those messages has its own lesson: [troubleshooting .yml compilation errors](lesson:3.3).
A Database Error works the other way round. dbt rendered your model and sent the SQL, and the warehouse rejected it. The message, like 001003 (42000): SQL compilation error, is the warehouse's own text, so its documentation may be what explains it. Don't be fooled by the word "compilation" here: this is the warehouse compiling SQL, not dbt compiling Jinja. Most of the time the fault is in the SQL of the model the error names. The error line compiled SQL at target/run/... points to the exact statement dbt ran. Opening that file next to the model is the method taught in [troubleshooting with compiled code](lesson:3.2).
Checkpoint 4 of 9· Check yourself
A run ends with Database Error in model customers (models/customers.sql) followed by 001003 (42000): SQL compilation error: syntax error line 14 at position 4 unexpected 'from'. What does this tell you?
A Database Error is the warehouse's message, passed on by dbt. "SQL compilation error" is the warehouse's own wording, and the usual cause is a mistake in the model's SQL.
“90% of the time, there's a mistake in the SQL of your model.”Source: docs.getdbt.com
Checkpoint 5 of 9· Exam question
A `dbt run` on `models/marts/finance/revenue.sql` fails with: Database Error in model revenue (models/marts/finance/revenue.sql) 001003 (42000): SQL compilation error: syntax error line 14 at position 4 unexpected 'from'. compiled SQL at target/run/jaffle_shop/models/marts/finance/revenue.sql The `.sql` file itself looks syntactically fine at line 14. What should the analytics engineer do next to isolate whether the bug is in the source SQL or in dbt's Jinja rendering?
Correct answer: A — Open the file at `target/run/.../revenue.sql` and inspect the fully rendered SQL dbt actually sent to the warehouse, since that copy reflects any Jinja or macro output the raw model file does not show.
- A. The error message itself points to this exact path, and it is the correct next step because it shows the fully rendered SQL including any dbt-generated wrappers, macro output, or Jinja substitutions the source file hides. Line 14 in that file is the line the warehouse actually complained about, not line 14 of the hand-written source.
- B. This is incorrect because stale compiled artifacts are not the typical cause of a warehouse-reported syntax error, and dbt regenerates `target/run` on every invocation anyway. Deleting `target/` and rerunning does not reveal anything the compiled file inspection would not already show.
- C. This is incorrect because a `log()` call only prints whatever string is passed to it, not the raw pre-Jinja file contents, so it would not surface the rendered SQL that caused the failure. The compiled file already provides exactly this information without any extra instrumentation.
- D. This is a plausible manual narrowing technique but it is slower and less direct than reading the compiled SQL dbt already generated, and it risks masking the real cause if the syntax error originates from Jinja logic rather than a specific CTE. The compiled file already isolates the problem without editing the source model.
Sources1
4.logs/dbt.log: the full record of a run
dbt writes every event to two places at once. The terminal (stdout) gets short, live progress lines. logs/dbt.log gets far more. The text log file holds every DEBUG-level event, with the log level and thread name on each line. It also holds every query dbt ran, not just the one that failed. The newest errors are at the bottom of the file. By default logs/ sits next to dbt_project.yml, and the --log-path flag moves it.
21:21:16.190990 [debug] [MainThread]: Partial parsing enabled: 0 files deleted, 0 files added, 0 files changed.
21:21:16.191404 [debug] [MainThread]: Partial parsing enabled, no changes found, skipping parsing
21:21:16.207330 [info ] [MainThread]: Found 2 models, 0 tests, 0 snapshots, 1 analysis, 535 macros, 0 operations, 1 seed file, 0 sources, 0 exposures, 0 metrics, 0 groupsThe log matters most when a Database Error comes from SQL that isn't in any model file. dbt also runs queries of its own: introspective queries that list objects, create schema statements, pre-hooks, post-hooks, on-run-start and on-run-end hooks, and the merge, update and insert statements behind incremental models and snapshots. You won't see that SQL by reading your model, so the log is where to find it. In the dbt platform, the same detail is under Details for a model in the command output. For a failed job, expand the run steps in Run History to see the console and debug logs.
A busy log is hard to read, so cut it down to one failure. Empty the file, run dbt again for the one model that fails, and the log holds only that run.
Checkpoint 6 of 9· Put it in order
A strange Database Error keeps showing up and the log is thousands of lines long. Put these steps in order to isolate it.
- 1.Re-execute dbt run for just the problematic model
- 2.Open logs/dbt.log and delete its contents
- 3.Read the log, which now holds only the queries from that one run
Clearing the file first means the next run for one model writes only the output you're looking for.
“clean out your logs by opening the file, and deleting the contents. Then, re-execute dbt run for just the problematic model.”Source: docs.getdbt.com
Checkpoint 7 of 9· Exam question
A `dbt run` fails before executing any SQL with: Found a cycle: model.jaffle_shop.customers --> model.jaffle_shop.stg_customers --> model.jaffle_shop.customers Which of the following changes would resolve this circular dependency? (Select all that apply)(Select 2)
Correct answers: A, B — Remove the `{{ ref('customers') }}` call from `stg_customers.sql` and rewrite that logic so `stg_customers` no longer needs to query the downstream `customers` model.; Replace any self-referential column lookup inside `customers.sql` with the `{{ this }}` Jinja variable instead of calling `{{ ref('customers') }}` on itself.
- A. This directly breaks the cycle: the graph shows `stg_customers` depending on `customers`, which in turn depends back on `stg_customers`, so removing the upstream `ref()` from the staging model eliminates one edge of the loop. Once only one direction of dependency remains, dbt can resolve the DAG.
- B. This is a standard fix for models that need to reference their own previously built state, such as incremental deduplication logic, since `{{ this }}` points at the model's own relation without creating a graph edge back to itself. Using `{{ this }}` instead of a self-`ref()` removes that edge from the cycle entirely.
- C. This is incorrect because materialization type has no effect on dbt's graph validation step, which runs before any SQL is compiled or executed. Ephemeral models still participate fully in cycle detection since the cycle exists in the `ref()` graph, not in how the model is materialized.
- D. This is incorrect because `--full-refresh` changes how existing tables are rebuilt during execution, but the cycle is detected during graph validation, a step that happens before execution begins. No amount of rerunning resolves a structural cycle in the declared `ref()` edges.
- E. This is incorrect because dbt's dependency graph is built from `ref()` and `source()` calls resolved to unique model names, not from the physical file name on disk. Renaming the file without updating the `ref()` calls inside it would not change the graph edges at all.
- F. This is incorrect because the `--empty` flag changes what rows are returned by refs and sources at execution time, but it does not disable or bypass the graph validation step where cycle detection happens. The cycle would still be reported before any SQL runs.
5.Controlling log output and reading exit codes
You can choose how much detail reaches each destination. --log-level sets the lowest severity written to both the terminal and the log file. The levels are debug, info, warn, error and none. --log-level-file sets the file on its own, so --log-level-file none stops writing the file but keeps terminal output. --debug (or -d) prints debug-level lines in the terminal too. That's useful when you can't easily open the file, such as in a CI log, but the output is long. --quiet (-q) goes the other way and shows only errors. Anything passed to the print() macro still appears, which makes --quiet handy when you're tracking down a Jinja bug with print() calls.
| Flag | Effect |
|---|---|
| --log-level debug | Minimum severity for both console and file logs |
| --log-level-file error | Sets the file log level separately from the console |
| --debug / -d | Sends debug-level logs to stdout as well as logs/dbt.log |
| --quiet / -q | Shows only error logs in stdout, plus print() output |
| --log-format json | Fully structured JSON log lines instead of text |
| --log-path | Moves the logs directory from its default location |
Log format is a separate setting from log level. --log-format takes text, json or default and applies to both the terminal and the file. --log-format-file sets the file on its own. The otel format streams OpenTelemetry-style telemetry to the console only, and setting --log-format-file otel does nothing.
Checkpoint 8 of 9· Fill the gap
You want debug-level events shown in the terminal as structured JSON so a CI tool can parse them. Which flag completes the command?
dbt build ? --log-format json--debug sends debug-level logs to stdout, and --log-format json makes them structured. --quiet does the opposite and hides everything except errors.
Source: docs.getdbt.comScripts and orchestrators often skip the log and look only at the exit code. 0 means the command finished with no errors. 1 means dbt finished but hit at least one handled error, such as a model syntax error or bad permissions. Some models may have been skipped. 2 means something unhandled stopped dbt partway, such as a ctrl-c or a network interruption. Any exit code other than zero means failure, so a CI step should treat 1 and 2 both as failures. Only 2 hints that the run itself was cut short.
Checkpoint 9 of 9· Check yourself
A nightly dbt build exits with code 1. What do you know before opening any logs?
Exit code 1 means a handled error in a run that finished. Interruptions like ctrl-c or network failures return 2.
“The run was completed, but some models may have been skipped.”Source: docs.getdbt.com
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A Database Error that says "SQL compilation error" means dbt failed to compile the model's Jinja.Why is that wrong?
Database Errors come from the warehouse, and dbt passes them on unchanged. The "compilation" in the message is the warehouse's, and the fault is usually in the model's SQL. Jinja problems in dbt are raised as Compilation Errors.
Covered in Compilation Error or Database Error: is it dbt or the SQL?
2.The latest failure is at the top of logs/dbt.log.Why is that wrong?
dbt adds new entries to the end of the log, so the newest errors are at the bottom.
Covered in logs/dbt.log: the full record of a run
3.Exit code 2 is just a more serious version of exit code 1, such as several failed models instead of one.Why is that wrong?
Exit code 1 means handled errors in a run that finished. Exit code 2 means an unhandled interruption, such as ctrl-c or a network drop, however many models failed.
4.--quiet hides everything except errors, including output from print() calls you added to debug a macro.Why is that wrong?
--quiet hides non-error logs, but anything passed to the print() macro still shows in the output.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://docs.getdbt.com/guides/debug-errorsOfficial docs
“will normally contain the type of error (more on these error types below), and the file where the error occurred”
↩︎ What a dbt error message tells you“Check that this a dbt project, and that dbt can connect to the warehouse”
↩︎ What a dbt error message tells you“Use pwd to check that you're in the right directory.”
↩︎ Runtime and Dependency errors: before any SQL runs“After updating the credentials, run dbt debug to check you can connect”
↩︎ Runtime and Dependency errors: before any SQL runs“If you need to reference the current model, use the {{ this }} variable instead.”
↩︎ Runtime and Dependency errors: before any SQL runs“Here, we rely on the Jinja library to pass back an error, and then just pass it on to you.”
↩︎ Compilation Error or Database Error: is it dbt or the SQL?“These errors come from your data warehouse, and dbt passes the message on.”
↩︎ Compilation Error or Database Error: is it dbt or the SQL?“90% of the time, there's a mistake in the SQL of your model.”
↩︎ Compilation Error or Database Error: is it dbt or the SQL?“In these cases, you should check out the logs — this contains all the queries dbt has run.”
↩︎ logs/dbt.log: the full record of a run“will normally contain the type of error (more on these error types below), and the file where the error occurred”
↩︎ Key concept“These errors come from your data warehouse, and dbt passes the message on.”
↩︎ Exam trap 1“Recent errors will be at the bottom of the file.”
↩︎ Exam trap 2“Compile the dependencies into a graph. Check that it's acyclic.”
↩︎ Checkpoint“Check the profile: key in your dbt_project.yml.”
↩︎ Checkpoint“depends on a node named 'stg_customer' which was not found”
↩︎ Prediction“clean out your logs by opening the file, and deleting the contents. Then, re-execute dbt run for just the problematic model.”
↩︎ Checkpoint - 2.
“Any models downstream of this model will also be skipped.”
↩︎ What a dbt error message tells you - 3.https://docs.getdbt.com/reference/events-loggingOfficial docs
“The text-formatted log messages in this file include all DEBUG-level events, as well as contextual information, such as log level and thread name.”
↩︎ logs/dbt.log: the full record of a run“The location of this file can be configured via the log-path flag.”
↩︎ logs/dbt.log: the full record of a run - 4.
“expand the run steps to view console and debug logs to review the detailed log messages.”
↩︎ logs/dbt.log: the full record of a run - 5.
“The available options for the log levels are debug, info, warn, error, or none.”
↩︎ Controlling log output and reading exit codes“This has the effect of showing debug-level log information in the terminal in addition to the logs/dbt.log file.”
↩︎ Controlling log output and reading exit codes“You can use the QUIET config to show only error logs in stdout.”
↩︎ Controlling log output and reading exit codes“Setting --log-format-file otel has no effect.”
↩︎ Controlling log output and reading exit codes“Logs will still include the output of anything passed to the print() macro.”
↩︎ Exam trap 4 - 6.https://docs.getdbt.com/reference/exit-codesOfficial docs
“a zero exit code will always imply success whereas a nonzero exit code will always imply failure.”
↩︎ Controlling log output and reading exit codes“The dbt invocation completed with an unhandled error (eg. ctrl-c, network interruption, etc).”
↩︎ Exam trap 3“The run was completed, but some models may have been skipped.”
↩︎ Checkpoint