dbt-labs/dbt-core · error
get_table_options: Failed to deserialize InternalDbtNodeWrap
Error message
get_table_options: Failed to deserialize InternalDbtNodeWrapper: {e} What it means
When handling the `get_table_options` method, the object converts the Jinja `local_config` value into the typed `InternalDbtNodeWrapper` struct via serde. If the value's shape does not match the expected struct (missing `__dbt_internal_node` fields, wrong nesting, plain dict instead of internal node), the deserialization fails and this SerdeDeserializeError is raised, embedding the underlying serde message.
Source
Thrown at crates/dbt-adapter/src/relation/relation_object.rs:772
return Err(minijinja::Error::new(
minijinja::ErrorKind::InvalidOperation,
"'materialized_view_from_relation_config' can only be invoked using the BigQuery adapter",
));
}
let iter = ArgsIter::new(
"Relation.materialized_view_from_relation_config",
&["local_config"],
args,
);
let local_config_value = iter.next_arg::<&Value>()?;
iter.finish()?;
let local_config = minijinja_value_to_typed_struct::<InternalDbtNodeWrapper>(
local_config_value.clone(),
)
.map_err(|e| {
minijinja::Error::new(
minijinja::ErrorKind::SerdeDeserializeError,
format!(
"get_table_options: Failed to deserialize InternalDbtNodeWrapper: {e}"
),
)
})?;
let loader =
crate::relation::bigquery::config::relation_types::materialized_view::new_loader();
let relation_config = loader
.from_local_config(local_config.as_internal_node())
.map_err(|err| {
minijinja::Error::new(
minijinja::ErrorKind::InvalidOperation,
format!("error while loading local materialized view config: {err}"),
)
})?;
Ok(Value::from_object(relation_config))View on GitHub (pinned to 0267ce9170)
Solutions
- Pass the internal node wrapper (e.g. `config.model` as provided by dbt's internal machinery), not a plain dictionary.
- Inspect the embedded serde message in the error to identify which field of InternalDbtNodeWrapper did not match and fix the caller accordingly.
- Upgrade/align the custom macro with the expected internal node structure for the installed dbt version.
Example fix
// before
{{ relation.get_table_options(config.model.config) }}
// after
{{ relation.get_table_options(config.model) }} Defensive patterns
Strategy: validation
Validate before calling
// Verify the argument is the internal node wrapper before calling
{% if config.model is none %}{% do exceptions.raise_compiler_error('model node required') %}{% endif %} Type guard
// Rustn is_internal_node(v: &Value) -> bool {
v.get_attr("__dbt_internal_node").is_ok()
} Try / catch
match minijinja_value_to_typed_struct::<InternalDbtNodeWrapper>(v.clone()) {
Ok(w) => ...,
Err(e) => log::warn!("bad node wrapper: {e}"),
} Prevention
- Always pass dbt's internal node objects to get_table_options, never plain dicts.
- Pin adapter and dbt-core versions together to avoid internal-shape drift.
- Read the embedded serde error to pinpoint the mismatched field.
When it happens
Trigger: Calling `relation.get_table_options(local_config)` where the `local_config` argument is a plain config dict / wrong value rather than an `InternalDbtNodeWrapper` produced by the node machinery.
Common situations: Passing `config.model` directly instead of the internal node wrapper; constructing the argument manually in a custom macro; version mismatch where the internal node shape changed between dbt versions.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- get_seed_file_path: Failed to deserialize DbtSeed: {e}
- Failed to deserialize InternalDbtNodeWrapper: {e}
- adapter.parse_partition_by failed on {raw_partition_by:?}: {
- model_columns: {e}
- model_constraints: {e}
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/7cf4da9f83629b17.
Report an issue: GitHub.