dbt-labs/dbt-core · error

attrs_updater should only be called once

Error message

attrs_updater should only be called once

What it means

Panic from `.expect()` after `attrs_updater.take()` in `find_and_record_span_status_from_attrs`. The updater is passed as `Option<impl FnOnce>` and must be consumed exactly once; a `None` here means the function's internal calling convention was violated (updater already taken or never supplied).

Source

Thrown at crates/dbt-tracing/src/span_info.rs:431

{
    let mut attrs_updater = Some(attrs_updater);
    with_current_span(move |span_ref| {
        // Find the closest span with the expected TelemetryAttributes type.
        // Scope iterator starts from the current span and goes up to the root.
        for span_ref in span_ref.scope() {
            let mut span_ext_mut = span_ref.extensions_mut();

            if let Some(attrs) = span_ext_mut
                // Get the current attributes
                .get_mut::<TelemetryAttributes>()
                .expect("Telemetry hasn't been properly initialized. Missing span event attributes")
                // Try downcasting to the expected type
                .downcast_mut::<A>()
            {
                // Found the expected attributes, call the updater and return
                attrs_updater
                    .take()
                    .expect("attrs_updater should only be called once")(attrs);

                // Record the status of the span from the attrs themselves
                if let Some(status) = attrs.get_span_status() {
                    span_ext_mut.replace(status);
                }

                return;
            }
        }
    });
}

/// Reads span start info from the given span with read-only access.
///
/// This provides immutable access to the span's start information including
/// trace_id, span_id, span_name, and other metadata.
///
/// Returns `None` if span is disabled.

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Ensure every call to `find_and_record_span_status_from_attrs` supplies a fresh `Some(updater)` closure.
  2. Audit the calling path (`record_test_span_with_detail`, `execute_unit_test_remote`, `process_unit_test_result`) for double invocation of the same updater value.
  3. If hit after a refactor, restructure so the updater is moved into the function rather than shared.

Example fix

// before
find_and_record_span_status_from_attrs(span, None);

// after
find_and_record_span_status_from_attrs(span, Some(Box::new(|attrs| { attrs.set_status(...); })));
Defensive patterns

Strategy: validation

Validate before calling

// assert updater supplied exactly once before calling
assert!(attrs_updater.is_some(), "updater must be Some");
find_and_record_span_status_from_attrs(span, attrs_updater);

Prevention

When it happens

Trigger: Calling `find_and_record_span_status_from_attrs` with a `None` updater, or a code path that invokes the function twice reusing the same consumed updater Option — only reachable through an internal caller bug since the public wrappers always construct the updater.

Common situations: Custom forks or patched call sites that pass `None` or double-call the function; refactors that moved the `take()` call without preserving the exactly-once contract.

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


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