dbt-labs/dbt-core · error · minijinja::Error (InvalidOperation)

Failed to convert payload to string

Error message

Failed to convert payload to string

What it means

After checking arity, write() converts the first argument with as_str(); if the payload is not a string (e.g. a dict, list, or number value from Jinja), conversion fails and this error is raised instead of writing garbage to disk.

Source

Thrown at crates/dbt-jinja-utils/src/phases/run/run_node_context.rs:700

impl Object for WriteConfig {
    fn call(
        self: &Arc<Self>,
        _state: &State<'_, '_>,
        args: &[MinijinjaValue],
        _listeners: &[Rc<dyn RenderingEventListener>],
    ) -> Result<MinijinjaValue, Error> {
        if args.is_empty() {
            return Err(Error::new(
                ErrorKind::InvalidOperation,
                "write function requires payload argument".to_string(),
            ));
        }

        // Extract payload from args
        let payload = match args[0].as_str() {
            Some(s) => s,
            None => {
                return Err(Error::new(
                    ErrorKind::InvalidOperation,
                    "Failed to convert payload to string".to_string(),
                ));
            }
        };

        // Write the file
        match write_file(&self.run_file_path, &self.resource_type, payload) {
            Ok(_) => {}
            Err(e) => {
                return Err(Error::new(
                    ErrorKind::InvalidOperation,
                    format!("Failed to write file: {e}"),
                ));
            }
        }

        // Return empty string on success

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Stringify the payload before calling: {{ write(payload | tojson) }} for structured data.
  2. Use {{ write(payload | string) }} for simple values.
  3. Inspect the payload type with |tojson or a type test to confirm what you are passing.

Example fix

-- before
{{ write({'status': 'ok'}) }}
-- after
{{ write({'status': 'ok'} | tojson) }}
Defensive patterns

Strategy: type-guard

Validate before calling

-- Jinja
{% if payload is not string %}
  {{ write(payload | tojson) }}
{% else %}
  {{ write(payload) }}
{% endif %}

Type guard

-- Jinja: only call write when payload is a string
{% if payload is string %}{{ write(payload) }}{% endif %}

Try / catch

match args[0].as_str() {
    Some(s) => write_file(&path, &rt, s)?,
    None => Err(Error::new(ErrorKind::InvalidOperation, "payload must be a string; use |tojson or |string")),
}

Prevention

When it happens

Trigger: Calling write() with a non-string first argument, e.g. {{ write({'key': 'value'}) }} or {{ write(my_list) }}, or a numeric value without |string.

Common situations: Passing structured data (dicts/lists) straight to write instead of rendering it; forgetting the |tojson or |string filter; macro returning an object rather than text.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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