What you will be able to do
- Explain how dbt resolves a ref when deferral is on, and why it uses only one environment per invocation
- Choose between No deferral, This job (self) and another environment for a merge or CI job
- Diagnose a CI failure caused by self-deferral and keep the comparison manifest fresh
- Pick the right deferral mechanism outside platform jobs: the Studio IDE toggle, the platform CLI, defer-env-id, defer_to_target or a manual --state
Key concept
Deferral target — Deferral target: the environment, or the job's own last run, whose manifest dbt uses when it resolves references to models it is not building. The target decides which state dbt compares changes against and where unbuilt parents are read from.
1.What deferral resolves, and in what order
Deferral lets a job or a developer build only the models they changed. Upstream parents are not rebuilt; their references point at relations that already exist elsewhere. dbt does this by reading a manifest from another run and resolving ref() calls against it. Choosing a deferral type is really choosing which manifest that is. The wrong choice shows up as a model reading stale data, or as a run that fails because the relation it points at no longer exists.
When deferral is on, dbt looks for a parent in a fixed order. A development version of the relation wins if one exists. Without one, dbt uses the staging environment's metadata. With no staging environment, it falls back to production. Only one environment is consulted per invocation, so a project with a staging environment never mixes staging and production parents in the same run. Passing --favor-state skips the first step and always resolves to the deferred environment, even when a development copy exists.
Deferral applies only to nodes you did not select. A selected model is always built in your own target. An unselected parent is swapped for the state version only when it does not already exist in the database, or when --favor-state is set. That is why a reader of dev_alice.model_b can end up joined to prod.model_a. Mixed reads like this can also make tests such as relationships compare data across environments.
Checkpoint 1 of 8· Check yourself
A developer has an old copy of model_a in their development schema and runs model_b with deferral on. The project has both a staging and a production environment. Which model_a does dbt read?
Without --favor-state, dbt prefers an existing development relation. It falls back to staging, or to production when there is no staging environment, only if that relation is missing.
“If a development version of a deferred relation exists, dbt preferentially uses the development database location when resolving the reference.”Source: docs.getdbt.com
Checkpoint 2 of 8· Exam question
A dbt Cloud CI job on Snowflake intermittently fails with an error about a relation that belonged to a pull request merged and closed three days earlier. The CI job's deferral setting is configured as "This job (self)" rather than a production deploy job. Which change fixes the failure?
Correct answer: A — Point the CI job's deferral setting at the production deploy job so comparison resolves against a stable schema, not a temporary PR schema dropped after merge.
- A. Self-deferral points comparison at the CI job's own prior runs, whose temporary PR schemas are dropped once the PR merges, so a later PR referencing that history fails. Deferring to a production job instead gives a permanent schema that always exists.
- B. Thread count controls parallel model execution within a single run and has no effect on whether a schema from a different, already-closed pull request still exists in the warehouse.
- C. Rebuilding everything on every PR removes the benefit of comparison but does not fix the underlying cause, and it makes every CI run far slower and more expensive without addressing the dropped schema.
- D. Merge jobs run after a PR lands and typically drop or replace the PR's temporary schema rather than preserve it, so sequencing the CI job after a merge job does not restore the missing relation.
2.No deferral, self or an environment: the job-level choice
A dbt platform job has three deferral settings. The Compare changes against setting on a merge job defaults to the environment the job was created from. You can change it to No deferral, to This job (self-deferral), or to a different environment. A CI job has the same setting, labelled Compare changes against an environment (Deferral), and it defaults to Production if you created one. The environment-level setup behind this is covered in [configuring environment deferral](lesson:3.8).
Deferring to an environment is the default for good reason. The standard command for both job types is dbt build --select state:modified+, and state comparison needs a deferred environment to compare against. Pick an environment when the job should build only what changed relative to what that environment last applied. That is the normal case for CI and for continuous deployment on merge. One precondition applies: the environment must have at least one successful job run, or there is no manifest to compare against.
No deferral takes away that baseline. It suits a job that should not depend on another run's state: a full rebuild, or a first run in an environment where nothing has succeeded yet. Change the command when you choose it, because state:modified+ has nothing to compare against without a deferred environment.
This job (self) makes the job's own previous run the baseline, instead of an environment's. The details are in [configuring self-deferral](lesson:4.10). Self-deferral is a poor fit for CI, where each pull request builds into its own temporary schema.
That failure is the main warning against self-deferral in CI. Each CI run writes into a temporary dbt_cloud_pr_ schema, so a CI job's last run is always some earlier pull request. If that PR has merged, its schema may already be gone when the next CI run starts. The fix is to defer to production rather than to self.
Checkpoint 3 of 8· Check yourself
A team creates a merge job and leaves the default command dbt build --select state:modified+, but sets deferral to No deferral. What goes wrong?
state:modified needs a deferred environment as its baseline. No deferral removes it, so the command should be changed to match.
“Importantly, state comparison can only happen when there is a deferred environment selected to compare state to.”Source: docs.getdbt.com
Checkpoint 4 of 8· Exam question
An environment does not have a defer-env-id explicitly set, and the project's environments include only a Production environment with no Staging environment defined. When a job in this project runs with deferral enabled, which environment supplies the comparison artifacts?
Correct answer: A — The Production environment, because dbt falls back to Production metadata whenever no Staging environment is defined for the project.
- A. dbt Cloud prefers Staging environment metadata for deferral when it exists, and falls back to Production metadata when it does not, so with only a Production environment defined that environment supplies the comparison artifacts.
- B. Self-deferral is not the implicit default; it must be explicitly chosen as the job's deferral target, and doing so is generally discouraged because it can reference schemas from other closed pull requests.
- C. Deferral does not silently fail in this situation; the documented fallback behavior is to use Production metadata, so the job still gets comparison artifacts rather than building everything unscoped.
- D. dbt does not select a deferral source based on environment creation order; the fallback logic is specifically Staging-then-Production, not chronological, so an arbitrary first-created environment is never the source.
3.Keeping the deferred environment's manifest current
Built-in deferral targets an environment, not a particular job. When a CI job defers to Production, the manifest it compares against comes from the latest successful run of any job that updates artifacts there. If several jobs with different settings run in that environment, the CI comparison state can shift unpredictably. A deploy or merge that refreshes the manifest while other pull requests are still open can also make those runs pick up unrelated state:modified nodes, until the branches are updated.
There are two fixes, used together. Developers merge or rebase the latest base branch into their pull request. The team adds a merge job in the environment the CI job defers to, which runs dbt parse --no-partial-parse (or dbt compile) straight after each merge, so the manifest updates without warehouse work. If the merge also starts a long dbt build, a separate merge-triggered parse-only job updates the manifest before that build finishes. Production state can also be supplied by a deploy job, but never by a CI job: deferral compares against the most recent successful production job, excluding CI jobs. CI design overall is covered in [setting up a CI job with deferral](lesson:4.1).
| Situation | Deferral choice | Reason |
|---|---|---|
| CI job checking a pull request | Production environment (the default) | Compares PR code against what production last applied, so only modified code is checked |
| Merge job doing continuous deployment | The environment the job was created from (the default) | Builds only edited models and their downstream dependents |
| Job that must not depend on another run's state | No deferral | No baseline is used, so replace state:modified+ with a full selection |
| CI error naming a previous PR's schema | Switch from This job (self) to production | The earlier PR's temporary schema may have been dropped |
| CI picks up unrelated state:modified nodes | Keep deferring to the environment; add a merge job running dbt parse --no-partial-parse there | Refreshes the comparison manifest right after merges |
Checkpoint 5 of 8· Check yourself
CI runs on open pull requests keep selecting models the developers never touched, soon after other PRs merge. The CI job defers to Production. What is the recommended fix?
Deferral targets an environment, so the fix is to keep that environment's manifest current and bring the branches up to date. A job ID cannot be chosen, and self or no deferral removes the production baseline.
“Built-in deferral targets an environment, not a hand-picked job ID.”Source: docs.getdbt.com
Checkpoint 6 of 8· Exam question
A platform team on Enterprise+ wants to enable Advanced CI ("compare changes") on an existing CI job so pull requests show row and column-level data differences against production. Which of the following are true prerequisites or constraints they must confirm? (Select all that apply.)(Select 2)
Correct answers: A, B — The CI job and the production job must query the same database host or connection, since compare changes cannot reach production objects across a different host.; The account must be on an Enterprise or Enterprise+ plan with Advanced CI enabled, since standard plans do not expose the `dbt compare` option on CI jobs.
- A. Compare changes runs comparison queries against production objects using the CI job's credentials, so the CI and production jobs must share the same database host; a different host makes those objects unreachable.
- B. Advanced CI is gated to Enterprise and Enterprise+ accounts with the feature enabled, and the CI job must have the `dbt compare` option turned on; it is not available on lower plan tiers.
- C. Compare changes works across normal dbt materializations including tables and incremental models, not only views, so restricting the project to view materializations is not a real requirement.
- D. Compare changes does not automatically enforce row-level security or masking; documentation instead warns that cached comparison data can lose masking outside the warehouse, so teams must apply their own protections.
4.Deferral outside platform jobs: IDE, CLI and self-managed runs
Deferral is not only a job setting. Where dbt runs decides which mechanism is available, and each one targets its environment differently:
- Studio IDE: the Defer to staging/production toggle pulls the latest manifest from staging, or from production if there is no staging environment, and adds --defer to commands that accept it.
- dbt platform CLI: deferral is on for every invocation, compared with production artifacts; --no-defer turns it off.
- defer-env-id in the dbt-cloud block of dbt_project.yml: points the platform at a different environment, such as staging. Because it is manifest-based, it disables dbt State-powered state:* selectors.
- defer_to_target in profiles.yml: for self-managed deployments with dbt State. It names the target that dbt State defers to; without it, every target defers to prod.
- Manual --state / --defer-state: points dbt at a specific manifest.json that you supply.
The IDE has one gotcha worth remembering. When a Staging environment exists, the IDE defers to it. If no deploy job has run there yet, defer does not work at all, even with a healthy production environment, because the metadata it needs does not exist yet. Run a deploy job in Staging first.
my_project:
outputs:
dev:
type: snowflake
# ... dev connection settings
staging:
type: snowflake
# ... staging connection settings
uat:
type: snowflake
# ... uat connection settings
defer_to_target: staging
prod:
type: snowflake
# ... prod connection settings
target: prodManual deferral gives the most control, and it needs the most care. Both --defer and --state must be set, either as flags or as environment variables. Normally one manifest does both jobs: it is the baseline for state:modified and the place unbuilt parents are read from. Pass --defer-state as well to split them. For example, you can compare logical changes against one environment and fail over to the built relations of another.
Checkpoint 7 of 8· Match them up
Match each deferral mechanism to where it applies
Tap a term, then the definition that fits it.
The IDE uses a toggle and the platform CLI defers automatically. defer-env-id changes the platform's target environment, and defer_to_target is the self-managed equivalent in profiles.yml.
“To defer to an environment other than the default (for example, staging), add defer-env-id to the dbt-cloud block in dbt_project.yml.”Source: docs.getdbt.com
Checkpoint 8 of 8· Fill the gap
Which flag completes this manual deferred test run?
dbt run --select [...] --defer --state path/to/artifacts
dbt test --select [...] ? --state path/to/artifactsManual deferral needs both --defer and --state. --defer-state alone only changes which manifest is used for deferral; it does not turn deferral on.
Source: docs.getdbt.comExam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Self-deferral is a safe choice for a CI job, because the job always has its own last run to compare against.Why is that wrong?
A CI job's last run built into an earlier pull request's temporary schema, which can be dropped once that PR merges. Defer to a production job run instead.
Covered in No deferral, self or an environment: the job-level choice
2.A successful CI run can serve as the production state that later deferrals compare against.Why is that wrong?
Deferral compares against the most recent successful production job, and CI jobs are excluded.
Covered in Keeping the deferred environment's manifest current
3.On the dbt platform, set defer_to_target in profiles.yml to choose which environment a job defers to.Why is that wrong?
defer_to_target is only for self-managed deployments. On the platform, deferral is configured in the environment and job settings.
Covered in Deferral outside platform jobs: IDE, CLI and self-managed runs
4.Studio IDE defer will fall back to production when a Staging environment exists but has never had a deploy job run.Why is that wrong?
If a Staging environment exists, the IDE needs a successful deploy job there. Until one runs, defer does not work.
Covered in Deferral outside platform jobs: IDE, CLI and self-managed runs
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/docs/platform/about-deferOfficial docs
“Note that dbt only defers to one environment per invocation — either staging or production.”
↩︎ What deferral resolves, and in what order“Passing the --favor-state flag will always resolve refs using staging metadata if available; otherwise, it defaults to production metadata”
↩︎ What deferral resolves, and in what order“When using defer, it compares artifacts from the most recent successful production job, excluding CI jobs.”
↩︎ Keeping the deferred environment's manifest current“--defer is automatically enabled in the dbt platform CLI for all invocations, compared with production artifacts. You can disable it with the --no-defer flag.”
↩︎ Deferral outside platform jobs: IDE, CLI and self-managed runs“Note that dbt only defers to one environment per invocation — either staging or production.”
↩︎ Key concept“When using defer, it compares artifacts from the most recent successful production job, excluding CI jobs.”
↩︎ Exam trap 2“a Staging environment exists but no deploy job has run”
↩︎ Exam trap 4“If a development version of a deferred relation exists, dbt preferentially uses the development database location when resolving the reference.”
↩︎ Checkpoint - 2.
“It doesn’t exist in the database (or --favor-state is used).”
↩︎ What deferral resolves, and in what order“Tests that depend on multiple parents (for example, relationships), may run across environments.”
↩︎ What deferral resolves, and in what order“If --defer-state is not specified, deferral will use the manifest supplied to --state.”
↩︎ Deferral outside platform jobs: IDE, CLI and self-managed runs“Deferral requires both --defer and --state to be set”
↩︎ Deferral outside platform jobs: IDE, CLI and self-managed runs - 3.https://docs.getdbt.com/docs/deploy/merge-jobsOfficial docs
“To change the default settings, you can select No deferral, This job for self-deferral, or choose a different environment.”
↩︎ No deferral, self or an environment: the job-level choice“compare changes against the environment you created the job from”
↩︎ No deferral, self or an environment: the job-level choice“For deferral (which is the default), make sure there has been at least one successful job run in the environment you defer to.”
↩︎ No deferral, self or an environment: the job-level choice“run dbt compile or, to avoid warehouse work entirely, dbt parse --no-partial-parse in a dedicated environment”
↩︎ Keeping the deferred environment's manifest current“Importantly, state comparison can only happen when there is a deferred environment selected to compare state to.”
↩︎ Checkpoint - 4.https://docs.getdbt.com/docs/deploy/ci-jobsOfficial docs
“To fix this issue, select a production job run to defer to instead of self.”
↩︎ No deferral, self or an environment: the job-level choice“The latest successful run in the environment can come from any job that updates artifacts there.”
↩︎ Keeping the deferred environment's manifest current“Built-in deferral targets an environment, not a hand-picked job ID.”
↩︎ Keeping the deferred environment's manifest current“To fix this issue, select a production job run to defer to instead of self.”
↩︎ Exam trap 1“this is usually an indicator that you are not using a production job for your deferral and are instead using self.”
↩︎ Prediction - 5.
“If you set it, dbt State-powered state:* selectors are disabled.”
↩︎ Deferral outside platform jobs: IDE, CLI and self-managed runs“To defer to an environment other than the default (for example, staging), add defer-env-id to the dbt-cloud block in dbt_project.yml.”
↩︎ Checkpoint - 6.
“This configuration applies to self-managed dbt deployments.”
↩︎ Deferral outside platform jobs: IDE, CLI and self-managed runs“deferral is configured through your environment settings in the UI.”
↩︎ Exam trap 3