dbt-labs/dbt-core · error · InvalidOperation

zip requires at least 1 argument

Error message

zip requires at least 1 argument

What it means

zip() combines two or more sequences element-wise (optionally with a fillvalue/default for uneven lengths, matching Python's itertools.zip_longest). It requires at least one positional argument; calling it with none throws this InvalidOperation error before any zipping happens.

Source

Thrown at crates/dbt-jinja-utils/src/functions/base.rs:838

/// Return an iterator of tuples where each tuple contains the i-th element from each of the input iterables.
/// dbt's zip also supports a custom kwarg `fillvalue` (default=None) to match the longest iterable.
///
/// Args:
///     *iterables: Two or more iterables
///     fillvalue: (optional) Value to fill in shorter iterables, default=None
///
/// Example:
/// ```jinja
/// {% set list1 = [1, 2] %}
/// {% set list2 = ['a', 'b', 'c'] %}
/// {% set pairs = zip(list1, list2, fillvalue='N/A') %}
/// -- Returns [(1, 'a'), (2, 'b'), ('N/A', 'c')]
/// ```
pub fn zip_fn() -> impl Fn(&[Value], Kwargs) -> Result<Value, Error> {
    move |args: &[Value], kwargs: Kwargs| -> Result<Value, Error> {
        if args.is_empty() {
            return Err(Error::new(
                ErrorKind::InvalidOperation,
                "zip requires at least 1 argument",
            ));
        }

        let default = match (kwargs.get::<Value>("default"), args.get(1)) {
            (Ok(value), _) => Some(value),
            (_, Some(value)) => Some(value.clone()),
            _ => None,
        };

        // Try to convert each argument to an internal Vec<Value>
        let mut iterators: Vec<Vec<Value>> = Vec::new();
        for arg in args {
            match arg.try_iter() {
                Ok(iter) => iterators.push(iter.collect()),
                Err(_) => return Ok(default.unwrap_or_else(|| Value::from(()))),
            }

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Pass at least two lists to zip: zip(list1, list2)
  2. If lists may be absent, guard: zip(list1, list2) only when both are defined and sequences
  3. Pass empty lists instead of omitting arguments: zip([], [])

Example fix

// before
{% set pairs = zip() %}
// after
{% set pairs = zip(list1 or [], list2 or []) %}
Defensive patterns

Strategy: validation

Validate before calling

{% if list1 is not sequence or list2 is not sequence %}
  {{ exceptions.raise_compiler_error('zip needs at least two lists in ' ~ this) }}
{% endif %}
{% set pairs = zip(list1, list2) %}

Type guard

{% macro zip_safe(a, b) %}{{ return(zip(a if a is sequence else [], b if b is sequence else [])) }}{% endmacro %}

Prevention

When it happens

Trigger: Calling zip() with zero positional arguments, e.g. a dynamically built argument list that ended up empty, or zip(default='N/A') with kwargs only.

Common situations: Lists built via template loops that turned out empty-of-arguments rather than being empty lists; refactors dropping the list arguments; confusion between the 'default' kwarg and positional fillvalue handling.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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