This contract starts with 0.16.0. It is a breaking CI-interface change from 0.15.x. Earlier package versions and Action tags retain their behavior until upgraded.
| Default code | Meaning | Is there a completed check report? |
|---|---|---|
| 0 | No blocking findings under the configured policy | Yes |
| 1 | Destructive findings | Yes |
| 2 | Review required | Yes |
| 3 | Execution could not complete | No reliable completed verdict |
This table describes check and run. Setup commands and --help/--version
exit 0 on success without producing an analysis report. Invalid CLI arguments
also exit 3, including errors normally reported by argparse as 2. Unsupported,
empty, or unknown --select terms now exit 3 without a completed report, even
when combined with valid terms. Earlier versions could warn and return 0. A known
but unchanged model remains a valid empty selection; see selection.
A review finding includes unparseable or non-UTF-8 model SQL, unknown rules, incomplete baselines, stale inputs, and potentially broken builds/tests that can be represented in a report. A missing required directory, unreadable current manifest/config, failed compile or recovery, or unexpected exception is an execution failure. Fix the error on stderr and rerun. Do not accept stdout as a completed report when the process failed, even if it wrote partial output first.
The default remains warning_exit_code: 2. File/environment configuration and
warning codes keep their existing behavior. warning_exit_code: 0 allows review
findings without hiding them from the report; execution failures still exit 3.
Other custom warning codes remain supported, except 3, which is now reserved.
The resolved configuration is checked after environment overrides. An unreadable
configuration fails instead of silently applying defaults.
For check and run, choose --fail-on destructive|warning|never to set an
explicit exit policy. Resolution is CLI flag > DBT_PLAN_FAIL_ON > fail_on
in .dbt-plan.yml > legacy behavior. With none supplied, warning_exit_code
keeps its existing behavior, including its default of 2 and opt-out value 0.
Resolved fail_on |
Destructive finding | Review finding | Execution error |
|---|---|---|---|
| Absent | 1 | warning_exit_code (default 2) |
3 |
warning |
1 | Configured nonzero warning_exit_code, or 2 when configured as 0 |
3 |
destructive |
1 | 0 | 3 |
never |
0 | 0 | 3 |
These policies apply to completed findings after the existing resource-specific
acknowledgements. They do not expand acknowledgement scope or change raw safety,
uncertainty, report contents, or summary counts. In particular, a parse failure
still requires review even when the exit policy permits it. run validates the
policy before invoking subprocesses or changing the worktree and passes it to
check. snapshot has no --fail-on option.
Only the exact lowercase values above are valid. The resolved value is validated
after all overrides: a valid higher layer may replace an invalid lower value,
but an invalid winning value is a configuration error (exit 3), before analysis
or side effects. An explicitly empty environment variable overrides the file
and is invalid unless a valid CLI flag overrides it. Empty file values and CLI
arguments are also invalid. Unreadable configuration and reserved
warning_exit_code: 3 remain errors even with --fail-on never.
dbt-plan check --fail-on warning
dbt-plan run --fail-on destructive
DBT_PLAN_FAIL_ON=never dbt-plan check --format json
The Action’s fail-on: destructive|warning|never and generated workflow’s
FAIL_ON own the CI gate. Their analysis and rendering calls override the new
file/environment fail_on setting while retaining legacy warning_exit_code
and resource acknowledgements. In particular, warning_exit_code: 0 still opts
out of warnings even when the Action input is warning. This differs deliberately
from the explicit CLI --fail-on warning, which forces warnings to fail.
The wrappers set the override before reading configuration; an invalid lower
fail_on cannot prevent that override. Other configuration errors still fail.
Only an environment variable is used, so pinned older CLIs need no new option
and keep their existing behavior.
The Action’s verdict output now describes raw JSON findings independently of
its exit-code output. Acknowledged destructive findings can therefore produce
verdict=destructive with exit-code=0; warning opt-out produces
verdict=warning with exit-code=0. The gate uses the policy code, not the raw
verdict. A zero code does not establish safety. The generated Check step exposes
the same separate outputs.
Policy applies only after a completed check; code 3 fails the Action Check step
before the Gate step, even for never. The wrappers require consistent JSON
summary counts and model safety values, and validate uncertainty fields, so
legacy package errors that exit 1 or 2 without a report still fail the step. The Action
continues to recognize standard verdict codes 0/1/2; custom warning codes outside
those values fail its Check step. Custom policies can change the exit status, so
inspect the report’s findings rather than treating code 0 as proof of safety.
In 0.15.x, failures commonly exit 2 and uncaught Python exceptions exit 1. Scripts
that permit code 2 as a warning cannot reliably detect those failures. Pin the
existing package until ready, then upgrade package and Action references together.
For a shell consumer using standard warning codes, capture the command status
explicitly so set -e does not bypass policy handling:
code=0
dbt-plan check --format json > dbt-plan-report.json || code=$?
case "$code" in
0) ;; # passed the configured policy
1) echo "Destructive findings" >&2; exit 1 ;;
2) echo "Review required; inspect the report" >&2 ;; # explicit allow-warning policy
3) echo "Check failed; fix stderr errors and rerun" >&2; exit 3 ;;
*) echo "Unexpected status: $code" >&2; exit "$code" ;;
esac
A bare CLI command still fails on any nonzero status. Newly generated ci-setup
workflows use an explicit FAIL_ON: destructive gate: they allow completed warnings
by default, and never allow execution errors. Report rendering cannot bypass that
gate. Existing workflow files are not rewritten by a package upgrade; regenerate
and review your project-specific settings to adopt this policy. See the
CI guide for dependency layouts and policy settings.
Update the dbt-plan section in consumer AGENTS.md after upgrading, or remove that
section and rerun dbt-plan agent-setup to regenerate it.
We chose a process code instead of only a JSON error envelope because shell consumers need the distinction too. The JSON report schema is unchanged; errors remain diagnostics on stderr. The 0.16.0 minor boundary follows the project’s pre-1.0 status (Semantic Versioning), while this guide makes the compatibility change explicit.
In 0.16.0, acknowledging an upstream model also waived the cascade findings
printed under that model. Starting with 0.17.0, an acknowledgement covers only
the named resource’s known findings. Review existing acknowledge_models lists
when upgrading; an upstream-only acknowledgement can now exit 1 or 2.
For example, stg_orders is a view that removes customer_id, and unchanged
fct_orders selects * with incremental sync_all_columns:
| CLI option | Exit | Explanation |
|---|---|---|
--acknowledge stg_orders |
1 | The inherited drop on fct_orders remains active. |
--acknowledge fct_orders |
0 | The drop is accepted; the view’s own replacement is safe. |
--acknowledge stg_orders,fct_orders |
0 | Both resources’ known findings are accepted. |
If stg_orders itself is incremental with sync_all_columns, downstream-only
acknowledgement still exits 1 for its own drop. Unrelated destructive findings
also still exit 1. Active warning impacts use warning_exit_code (default 2,
including the opt-out value 0). Action fail-on handling is unchanged; execution
errors still exit 3, even with all resource names acknowledged.
Use exact report names, including version suffixes or defined_in file names;
there are no globs or blanket acknowledgements. Repeating a name has no extra
effect, and one explicitly named target may be shared by several upstream
findings. Tests require their own exact resource name, never their parent model’s
name. Names shared by distinct manifest resources, including tests or exposures,
cannot waive findings; the report explains the ambiguity. Exposure owner lines
are informational and are not waived by a model acknowledgement.
Unknown risk kinds, unknown operations, unreadable test fixtures/SQL, unresolved inherited schemas or contracts, parse failures, incomplete baselines and other analysis refusals cannot be acknowledged away. They retain review-level uncertainty under the configured warning policy.
When acknowledgements are supplied, text and GitHub labels say
ACKNOWLEDGED: own findings only; each cascade line shows whether its affected
resource is waived or active. JSON retains every
existing field, raw safety, summary count and cascade detail. acknowledged
and the corresponding summary count still indicate requested model names, not
the absence of active descendants. Additive fields make policy explicit:
models[].own_safety, models[].own_waived, and
models[].downstream_impacts[].waived / waiver_reason (the latter two appear
when acknowledgements are supplied). A false own_waived
means the resource was not acknowledged or its findings/identity could not be
waived. MCP verdicts continue to reflect raw severity even when CLI policy exits 0.