dbt-labs/dbt-core · error
Expected a model node
Error message
Expected a model node
What it means
After the config value successfully deserializes into an InternalDbtNodeWrapper, `dynamic_table_config_changeset` requires it to be the Model variant. Any other node kind (e.g. seed, snapshot, test) hits the catch-all arm and produces this InvalidOperation error. It guards the downstream code that only knows how to build a dynamic-table changeset from a model node.
Solutions
- Only call dynamic_table_config_changeset from model nodes; remove the call from snapshot/seed contexts.
- If you need equivalent behavior for another node type, implement a branch for that wrapper variant instead of forcing it through the model path.
- Confirm the config you passed actually corresponds to the model in question, not another node.
Defensive patterns
Strategy: validation
Validate before calling
{% if node.resource_type != 'model' %}
{{ exceptions.warn('dynamic_table_config_changeset is model-only; skipping') }}
{% else %}
{{ relation.dynamic_table_config_changeset(config) }}
{% endif %} Type guard
fn is_model_wrapper(w: &InternalDbtNodeWrapper) -> bool {
matches!(w, InternalDbtNodeWrapper::Model(_))
} Try / catch
try:
relation.dynamic_table_config_changeset(config)
except minijinja.Error as e:
if 'Expected a model node' in str(e):
skip_changeset()
else:
raise Prevention
- Only call this method inside model materialization macros
- Gate calls on node.resource_type == 'model'
- Provide separate paths for snapshots/seeds
When it happens
Trigger: Invoking dynamic_table_config_changeset from a non-model node context (snapshot, seed, etc.) whose wrapper deserializes as a variant other than InternalDbtNodeWrapper::Model.
Common situations: Calling this relation method in a snapshot or seed macro, or reusing model relation macros in custom materializations attached to non-model nodes.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- adapter not found in context
- adapter should be configured for the parse phase
- compute_external_path: Failed to deserialize config
- compute_external_path: Failed to deserialize…
- describe_dynamic_table is not supported by the
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/589786bbecf9c447.
Report an issue: GitHub.
Appendix: source
Thrown at crates/dbt-adapter/src/relation/relation_impl.rs:1030
relation_config_value: &Value,
) -> Result<Value, minijinja::Error> {
match self.adapter_type {
AdapterType::Snowflake => {
// TODO(serramatutu): minijinja_value_to_typed_struct does not work with references, so we
// have to clone the value here...
let local_config = minijinja_value_to_typed_struct::<InternalDbtNodeWrapper>(
relation_config_value.clone(),
)
.map_err(|e| {
minijinja::Error::new(
minijinja::ErrorKind::SerdeDeserializeError,
format!("Failed to deserialize InternalDbtNodeWrapper: {e}"),
)
})?;
let local_config = match local_config {
InternalDbtNodeWrapper::Model(model) => model,
_ => {
return Err(minijinja::Error::new(
minijinja::ErrorKind::InvalidOperation,
"Expected a model node",
));
}
};
dynamic_table_config_changeset_from_local_config(
local_config.as_ref(),
relation_results_value,
)
}
_ => Err(minijinja::Error::new(
minijinja::ErrorKind::InvalidOperation,
"Only available for snowflake",
)),
}
}
View on GitHub (pinned to 0267ce9170)