dbt-labs/dbt-core · error

agate_table must be an agate.Table

Error message

agate_table must be an agate.Table

What it means

An InvalidOperation minijinja error thrown when the `agate_table` argument to the load-file/index-creation callable cannot be downcast to an AgateTable object. The callable requires the actual agate Table wrapper; any other Value (dict, string, None) fails the downcast.

Source

Thrown at crates/dbt-adapter/src/adapter/mod.rs:3757

                        "database",
                        "schema",
                        "table_name",
                        "file_path",
                        "agate_table",
                        "column_overrides",
                        "field_delimiter",
                    ],
                    args,
                );
                let database = iter.next_arg::<&str>()?;
                let schema = iter.next_arg::<&str>()?;
                let table_name = iter.next_arg::<&str>()?;
                let file_path = iter.next_arg::<&str>()?;
                let agate_table = iter
                    .next_arg::<&Value>()?
                    .downcast_object::<AgateTable>()
                    .ok_or_else(|| {
                        minijinja::Error::new(
                            minijinja::ErrorKind::InvalidOperation,
                            "agate_table must be an agate.Table",
                        )
                    })?;
                let column_overrides_val = iter.next_arg::<Value>()?;
                let column_overrides = minijinja_value_to_typed_struct::<IndexMap<String, String>>(
                    column_overrides_val,
                )
                .map_err(|e| {
                    minijinja::Error::new(
                        minijinja::ErrorKind::SerdeDeserializeError,
                        e.to_string(),
                    )
                })?;
                let field_delimiter = iter.next_arg::<&str>()?;
                iter.finish()?;

                if let adapter_impl::InnerAdapter::Replay(_, replay) = adapter.inner_adapter() {

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Pass the real `agate_table` object provided by the seed/load context (an actual agate.Table exposed to Jinja).
  2. Ensure your code path actually creates an agate.Table instead of a dict/list before calling.
  3. If the table may be absent, guard and skip the call rather than passing None.
  4. Verify the binding/wrapper layer converts agate tables into AgateTable Values.

Example fix

// before
create_indexes('t', 'path.csv', {'col': 'int'})
// after
create_indexes('t', 'path.csv', agate_table)
Defensive patterns

Strategy: type-guard

Validate before calling

{% if agate_table is defined and agate_table is not string and agate_table is not mapping %}
  {# likely a real agate table #}
{% endif %}

Type guard

fn is_agate_table(v: &Value) -> bool {
    v.downcast_object::<AgateTable>().is_some()
}

Try / catch

match val.downcast_object::<AgateTable>() { Some(t) => ..., None => Err(invalid_operation("agate_table must be an agate.Table")) }

Prevention

When it happens

Trigger: Calling the function (table_name, file_path, agate_table, ...) with agate_table set to a raw dict, a JSON string, None, or an object not wrapped as an AgateTable minijinja Value.

Common situations: Custom seed/index macros that build tables as plain dicts; passing `None` when the seed has no rows; Python objects that crossed the bridge without agate wrapping; dbt version differences in how agate tables are exposed to Jinja.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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