dbt-labs/dbt-core · error · minijinja::Error (InvalidOperation)
compiled_code must be a string
Error message
compiled_code must be a string
What it means
`submit_python_job` accepts the compiled Python code only as a string; the second argument is coerced via `as_str()` and this error is thrown when it is not a string value. The library throws it because the adapter's `submit_python_job` method forwards the value directly and cannot accept other types.
Solutions
- Pass the compiled code as a string, e.g. the rendered result of the python model's code macro.
- If the value is an object, serialize/render it to a string before calling: `{% do submit_python_job(model, compiled_code | string) %}`.
- Log/print the argument with `| string` in the template to verify it holds actual Python source.
Example fix
-- before
{% set code = my_python_code_macro(model) %}{% do submit_python_job(model, code) %}
-- after
{% set code = my_python_code_macro(model) | string %}{% do submit_python_job(model, code) %}
Defensive patterns
Strategy: type-guard
Validate before calling
{% if compiled_code is not string %}
{% set compiled_code = compiled_code | string %}
{% endif %}
{% do submit_python_job(model, compiled_code) %} Type guard
// minijinja
fn is_string_value(v: &MinijinjaValue) -> bool { v.as_str().is_some() } Try / catch
try:
submit_python_job(model, code)
except Error as e:
if "compiled_code must be a string" in str(e):
raise ValueError("pass rendered python source as a string") from e
raise Prevention
- Ensure the macro producing the code returns a string (pipe through | string if needed).
- Check for None/undefined upstream compilation failures before calling.
- Log the argument type during development with a debug print.
When it happens
Trigger: Calling `submit_python_job(model, some_non_string)` where compiled_code is e.g. a dict, a number, or an unset/undefined Minijinja value instead of the rendered Python source string.
Common situations: Passing a macro result object instead of its string; forgetting to `render()` or call the macro that produces the code; a variable holding `none` because compilation failed silently upstream.
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
- Argument must be a string
- Column 'data_type' must be a string
- Column 'name' must be a string
- Failed to convert payload to string
- get_csv_data: argument must be an AgateTable
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/33adcf1baae03aa1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/dbt-jinja-utils/src/phases/run/run_node_context.rs:801
format!("Failed to write to {}: {}", full_path.display(), e),
)),
}
}
/// Returns the function used for the submit_python_job context.
fn submit_python_job_context_fn()
-> impl Fn(&State, &[MinijinjaValue]) -> Result<MinijinjaValue, Error> + Copy {
|state: &State, args: &[MinijinjaValue]| {
// Parse arguments: submit_python_job(parsed_model, compiled_code)
if args.len() != 2 {
return Err(Error::new(
ErrorKind::InvalidOperation,
format!("submit_python_job expects 2 arguments, got {}", args.len()),
));
}
let parsed_model = &args[0];
let compiled_code = args[1].as_str().ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperation,
"compiled_code must be a string",
)
})?;
// Note(Ani):
// dbt-core validates:
// - macro_stack.depth == 2
// - call_stack[1] == "macro.dbt.statement"
// - "materialization" in call_stack[0]
//
// In fusion, we shouldn't need to do this because this funciton is only registered in the run node context
// so if a user tries to use it outside of a statement.sql macro, in a materialization macro, it will fail earlier due to an unrecongized function call.
// Get adapter from context and call submit_python_job
let adapter = state
.lookup("adapter", &[])
.ok_or_else(|| Error::new(ErrorKind::UndefinedError, "adapter not found in context"))?;View on GitHub (pinned to 0267ce9170)