dbt-labs/dbt-core · error
ModelPropertiesSemanticModelConfig is never finalized direct
Error message
ModelPropertiesSemanticModelConfig is never finalized directly
What it means
ModelPropertiesSemanticModelConfig is an intermediate 'draft' config that implements a finalize trait, but by design it is never finalized as-is; it must first be converted/merged into another config form. Calling finalize() on it directly indicates a pipeline bug where the wrong config type reached the resolution step. The panic is a guard that surfaces such misuse immediately instead of producing a half-resolved semantic model.
Source
Thrown at crates/dbt-schemas/src/schemas/properties/model_properties.rs:102
}
impl ResolvableConfig<SemanticModelConfig> for ModelPropertiesSemanticModelConfig {
type Resolved = ResolvedSemanticModelConfig;
type PackageDefaults = ();
type ResolveDefaults = ();
fn get_enabled_with_default(&self) -> bool {
self.enabled
}
fn disable(&mut self) {
self.enabled = false;
}
fn apply_package_defaults(&mut self, _: ()) {}
fn finalize(self) -> ResolvedSemanticModelConfig {
unreachable!("ModelPropertiesSemanticModelConfig is never finalized directly")
}
fn default_to(&mut self, parent: &SemanticModelConfig) {
DefaultTo::inherit_from(&mut self.group, &parent.group);
}
}
impl ModelProperties {
pub fn empty(name: String) -> Self {
Self {
name,
columns: None,
config: None,
constraints: None,
data_tests: None,
deprecation_date: None,
description: None,
identifier: None,View on GitHub (pinned to 0267ce9170)
Solutions
- Do not call finalize() on ModelPropertiesSemanticModelConfig directly; finalize the containing/wrapper config instead.
- Locate the resolution call site and ensure the semantic model config is converted to the final ResolvedSemanticModelConfig via the intended wrapper path.
- If adding a new config type, implement finalize() with a real conversion rather than routing through this placeholder.
- Check for recent refactors in properties model resolution that may have swapped config types.
Example fix
// before let resolved = semantic_cfg.finalize()?; // panics: draft type never finalized directly // after let resolved = properties.semantic_model.unwrap().finalize()?; // finalize the wrapper that owns the conversion
Defensive patterns
Strategy: type-guard
Validate before calling
if cfg.is_draft_intermediate() { /* route through wrapper, not finalize */ } Type guard
fn is_finalizable_directly<T: Finalize>(_cfg: &T) -> bool { false } // draft types are never finalized directly; finalize their wrapper instead Prevention
- Never call finalize() on intermediate/draft config types; finalize the wrapper that owns the conversion.
- Mark draft config types with #[doc(hidden)] or a naming convention signaling intermediacy.
- Prefer newtype wrappers so the wrong finalize target cannot typecheck.
When it happens
Trigger: Invoking DefaultTo/finalize resolution machinery with a ModelPropertiesSemanticModelConfig value instead of the wrapper type that is actually meant to be finalized.
Common situations: Refactoring the properties-resolution pipeline so a semantic model config bypasses its wrapper; writing new code that calls finalize() generically on any config implementing the trait; custom tooling that reuses dbt-schemas config types and calls finalize directly.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- when data_type is date, inner must be a TimeConfig
- cannot consume EOF
- synthetic relation construction should not fail: {e}
- Task should always operate on at least a single node
- Task that doesn't belong to a phase should override telemetr
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/785240f364e204df.
Report an issue: GitHub.