{"record":{"id":"e609eac2f3144f2b","repo":"dbt-labs/dbt-core","slug":"argument-must-be-a-string","errorCode":null,"errorMessage":"Argument must be a string","messagePattern":"Argument must be a string","errorType":"validation","errorClass":"InvalidOperation","httpStatus":null,"severity":"error","filePath":"crates/dbt-jinja-utils/src/functions/base.rs","lineNumber":741,"sourceCode":"    move |state: &State, args: &[Value], _kwargs: Kwargs| -> Result<Value, Error> {\n        if args.len() != 1 {\n            return Err(Error::new(\n                ErrorKind::InvalidOperation,\n                \"render requires exactly one argument (the string to render)\",\n            ));\n        }\n        // dbt-core (Jinja2/Python) effectively accepts any value here and stringifies it.\n        // In practice, many dbt projects call `render(...)` on values that can legitimately be\n        // `none` (e.g. optional metadata-driven SQL snippets from `run_query`), expecting\n        // `\"None\"` and handling that downstream.\n        //\n        // Fusion uses minijinja which is stricter by default; align behavior by accepting\n        // `none` and treating it like Python's `str(None)` => `\"None\"`.\n        let sql = if args[0].is_none() {\n            \"None\"\n        } else {\n            args[0].as_str().ok_or_else(|| {\n                Error::new(ErrorKind::InvalidOperation, \"Argument must be a string\")\n            })?\n        };\n\n        let env = state.env();\n\n        let template = env.template_from_str(sql)?;\n        let rendered = template.render(state.get_base_context(), &[])?;\n        Ok(Value::from(rendered))\n    }\n}\n\n/// Strict version of set() that fails if the input is not iterable.\n///\n/// Args:\n///     value: An iterable value to convert to a set\n///\n/// Example:\n/// ```jinja","sourceCodeStart":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-jinja-utils/src/functions/base.rs#L723-L759","documentation":"After the arity check, render() requires its single argument to be a string so it can compile it as a Jinja template. If the argument is any non-string, non-none value (number, dict, list, bool), as_str() fails and this InvalidOperation error is thrown. This mirrors stricter minijinja typing versus dbt-core's permissive Python stringification.","triggerScenarios":"Calling render(123), render(my_dict), render(my_list) or passing any Jinja value that is not a string and not none. Also occurs when a variable silently holds a non-string type (e.g. a number from a config).","commonSituations":"Variables read from YAML config or a query result that are numbers/objects rather than strings; assuming dbt-core's behavior of str()-ing any value carries over to Fusion; concatenation that produced a non-string container instead of a string.","solutions":["Convert the value to a string before calling: render(value | string)","For numbers/bools use ~ or |string interpolation to build the template string","Keep the none special case in mind: none is accepted and becomes 'None'"],"exampleFix":"// before\n{% set out = render(row_count) %}\n// after\n{% set out = render(row_count | string) %}","handlingStrategy":"type-guard","validationCode":"{% if value is not string and value is not none %}\n  {% set value = value | string %}\n{% endif %}\n{% set out = render(value) %}","typeGuard":"{% macro ensure_str(x) %}{{ return(x if x is string else (x | string)) }}{% endmacro %}","tryCatchPattern":null,"preventionTips":["Pipe non-strings through |string before render","Check var()/config() return types — they may be numbers or dicts","Do not rely on dbt-core's implicit str() behavior; Fusion is stricter"],"tags":["jinja","dbt","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"0267ce9170576975b76b64ce856b2e5848e96617","analyzedAt":"2026-09-07T21:53:39.732Z","contentChangedAt":"2026-09-07T21:53:39.732Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}