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
- Pass the real `agate_table` object provided by the seed/load context (an actual agate.Table exposed to Jinja).
- Ensure your code path actually creates an agate.Table instead of a dict/list before calling.
- If the table may be absent, guard and skip the call rather than passing None.
- 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
- Pass the context-provided agate table object from load_result/seed flows
- Ensure bridge code wraps Python agate.Table values as AgateTable
- Handle empty seeds by skipping the call instead of passing None
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
- group_by with function key
- model_config must be a RelationConfig
- ColumnsAsTuple::count_occurrences_of
- ColumnsAsTuple::index_of
- column_distinct
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/4283a200c751a8c5.
Report an issue: GitHub.