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

  1. Only call dynamic_table_config_changeset from model nodes; remove the call from snapshot/seed contexts.
  2. If you need equivalent behavior for another node type, implement a branch for that wrapper variant instead of forcing it through the model path.
  3. 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

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


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)