{"record":{"id":"9c2734b98bdb3463","repo":"dbt-labs/dbt-core","slug":"failed-to-serialize-merged-node-config-to-dbt-yaml","errorCode":null,"errorMessage":"Failed to serialize merged node config to dbt_yaml::Value for parse model.config","messagePattern":"Failed to serialize merged node config to dbt_yaml::Value for parse model\\.config","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/dbt-jinja-utils/src/phases/parse/resolve_model_context.rs","lineNumber":283,"sourceCode":"            compiled_code: None,\n        },\n        __adapter_attr__: AdapterAttr::default(),\n        __other__: BTreeMap::new(),\n        deprecated_config: ModelConfig::default(),\n    };\n\n    let mut model_map = convert_yml_to_value_map(InternalDbtNode::serialize(&model));\n    // Stub `DbtModel` uses `ModelConfig::default()` for `config` in YAML serialization. At parse\n    // time, kwargs to `config(...)` (e.g. `post_hook=my_macro(model)`) are evaluated while\n    // rendering; macros must see the merged node config (`properties_config` / `BaseConfig`),\n    // matching dbt-core (dbt-fusion#1414).\n    //\n    // Use `dbt_yaml::to_value` + `yml_value_to_minijinja` — same pipeline as\n    // `DbtModel::serialized_config()` — not `MinijinjaValue::from_serialize`, so later\n    // `dbt_yaml::to_value(model)` → `InternalDbtNodeWrapper::deserialize` in adapter helpers\n    // (`get_view_options`, `get_config_from_model`, …) round-trips correctly.\n    let config_yml = dbt_yaml::to_value(config)\n        .expect(\"Failed to serialize merged node config to dbt_yaml::Value for parse model.config\");\n    model_map.insert(\"config\".to_owned(), yml_value_to_minijinja(config_yml));\n    model_map.insert(\n        \"batch\".to_owned(),\n        MinijinjaValue::from_object(init_batch_context()),\n    );\n\n    let result_store = ResultStore::default();\n    let mut packages: BTreeSet<String> = runtime_config.dependencies.keys().cloned().collect();\n    packages.insert(root_project_name.to_string());\n\n    // Object-typed slots are wrapped via `MinijinjaValue::from_object(...)` /\n    // `MinijinjaValue::from_function(closure)` HERE rather than in the typed\n    // ctx struct, because going through serde's `serialize_map` /\n    // `serialize_seq` paths can change the underlying Object's concrete type.\n    // `builtins` must keep its BTreeMap shape for downstream downcasts, while\n    // `model` is intentionally mutable to match dbt Core's dict behavior.\n    let ctx = ResolveModelCtx {\n        this: this_value,","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-jinja-utils/src/phases/parse/resolve_model_context.rs#L265-L301","documentation":"When building the parse-time model context, the merged node config is serialized to a `dbt_yaml::Value` with `dbt_yaml::to_value` and the result is unwrapped with `expect`. Serialization of the merged config can only fail if the config contains values that cannot be represented in YAML (non-string map keys, unsupported types injected via `model.config(...)` jinja calls). The panic aborts parsing of that model.","triggerScenarios":"Calling `{{ config(...) }}` in a model with a value dbt_yaml cannot serialize — e.g. `{{ config(my_dict_with_int_keys) }}`, passing a minijinja object/function as a config value, or a merged config containing non-YAML-serializable types.","commonSituations":"Users passing dicts with mixed/numeric keys to config(); macros returning exotic objects into config(); jinja-set config values that are functions or custom objects rather than primitives/lists/dicts.","solutions":["Inspect the `{{ config(...) }}` calls in the failing model and ensure every value is a YAML-representable primitive, list, or string-keyed dict","Convert non-string keys to strings before passing objects into config()","Use `log()`/`print` in the model to dump the offending config argument and find the non-serializable value","At the code level, replace `expect` with an error carrying the serde message and the offending key/type"],"exampleFix":"// before (model.sql)\n{{ config(materialized=table, tags={1: 'x'}) }}\n// after\n{{ config(materialized='table', tags={'1': 'x'}) }}","handlingStrategy":"try-catch","validationCode":"// validate config values are YAML-serializable before calling dbt_yaml::to_value\nfn config_is_serializable<T: serde::Serialize>(config: &T) -> bool {\n    dbt_yaml::to_value(config).is_ok()\n}","typeGuard":null,"tryCatchPattern":"let config_yml = dbt_yaml::to_value(config).map_err(|e|\n    anyhow!(\"Failed to serialize merged node config for parse model.config: {e}\"))?;","preventionTips":["Only pass strings, numbers, booleans, lists and string-keyed dicts to {{ config() }}","Never pass functions or minijinja objects into config()","Test macros that build config dicts return plain serializable dicts","Log the offending config payload when serialization fails"],"tags":["panic","serialization","yaml","config","jinja"],"backgroundTag":"json-serialization-failed","analyzedSha":"0267ce9170576975b76b64ce856b2e5848e96617","analyzedAt":"2026-09-07T21:53:39.732Z","contentChangedAt":"2026-09-07T21:53:39.732Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}