dbt-labs/dbt-core · error

Unexpected phase

Error message

Unexpected phase: {}

What it means

While building the phased task graph for semantic-analysis tasks, cross-phase dependency edges are only expected from Render into the Run or Analyze phases. Any other phase reaching this match indicates the phase enum gained a new variant (or a phase was misrouted) without updating edge construction. The code panics on that unknown phase because no sensible edge target exists.

Solutions

  1. Extend the match in build_phased_task_graph with an arm for the new phase variant (printing the message shows which one).
  2. Fix node/dependency bucketing so dependencies only carry Run/Analyze phases where expected.
  3. Clear stale serialized graph/phase caches after upgrading dbt.
  4. Audit recent changes to the PhaseTaskVariant enum for missed match arms.

Example fix

// before
TP::Run => runnable_node_index_map.get(dep),
TP::Analyze => analyzeable_node_index_map.get(dep),
_ => unreachable!("Unexpected phase: {}", phase_to_dep),

// after
TP::Run => runnable_node_index_map.get(dep),
TP::Analyze => analyzeable_node_index_map.get(dep),
TP::NewPhase => new_phase_node_index_map.get(dep),
Defensive patterns

Strategy: validation

Validate before calling

assert!(matches!(phase_to_dep, TP::Run | TP::Analyze), "unexpected phase {}", phase_to_dep);

Type guard

fn is_edge_capable_phase(p: &TP) -> bool { matches!(p, TP::Run | TP::Analyze) }

Try / catch

match phase_to_dep {
    TP::Run => runnable_node_index_map.get(dep),
    TP::Analyze => analyzeable_node_index_map.get(dep),
    other => { log::warn!("unexpected phase {other}"); None }
}

Prevention

When it happens

Trigger: Calling build_phased_task_graph when a dependency's phase_to_dep is neither TP::Run nor TP::Analyze — i.e. a new PhaseTaskVariant was added to the enum but not to this match, or a node was bucketed into an unexpected phase.

Common situations: Upgrading dbt after a new task phase was introduced while cached/serialized phase metadata still carries the old shape; custom forks adding a phase variant without updating graph.rs; a bucketing bug assigning a dependency to the wrong phase.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/5dfd7fde6967eb21. Report an issue: GitHub.

Appendix: source

Thrown at crates/dbt-tasks-sa/src/graph.rs:418

            } else if phases.contains(&TP::Run) && buckets.in_baseline_or_off_closure(unique_id) {
                Some(TP::Run)
            } else {
                Some(TP::Analyze)
            };

            if let Some(phase_to_dep) = maybe_phase_to_dep {
                if let Some(deps) = schedule.deps.get(unique_id) {
                    for dep in deps {
                        if let Some(dep_phases) = node_to_phases.get(dep)
                        && dep_phases.contains(&phase_to_dep)
                        // Never depend on baseline analyze
                        && (phase_to_dep == TP::Run || !
                            buckets.in_baseline_closure(dep))
                        {
                            let maybe_from_idx = match phase_to_dep {
                                TP::Run => runnable_node_index_map.get(dep),
                                TP::Analyze => analyzeable_node_index_map.get(dep),
                                _ => unreachable!("Unexpected phase: {}", phase_to_dep),
                            };
                            if let (Some(&from_idx), Some(&render_idx)) = (
                                maybe_from_idx,
                                node_indices.get(&(TP::Render, unique_id.clone())),
                            ) {
                                graph.update_edge(from_idx, render_idx, ());
                            }
                        }
                    }
                }
            }
        }

        // Add test-to-model run dependencies if fail_fast is enabled - OPTIMIZED
        if let Some(run_nodes) = phase_to_nodes.get(&TP::Run) {
            add_test_to_model_dependencies(
                run_nodes,
                &schedule.deps,

View on GitHub (pinned to 0267ce9170)