windmill-labs/windmill · error · ValueError

preprocessor function is missing

Error message

preprocessor function is missing

What it means

backend/windmill-worker/src/python_executor.rs builds a Python wrapper for the job. When the job has a preprocessor expansion (pre_spread), the generated wrapper checks inner_script.preprocessor; if it is None or not callable it raises ValueError('preprocessor function is missing'). This happens when preprocessor args are supplied but the script itself defines no usable preprocessor function.

Source

Thrown at backend/windmill-worker/src/python_executor.rs:812

        job.preprocessed,
        job.script_entrypoint_override.as_deref(),
        inner_content,
        &script_path,
        &temp_script_refs,
    )
    .await?;

    tracing::debug!("Finished preparing wrapper");

    let apply_preprocessor = pre_spread.is_some();

    create_args_and_out_file(&client, job, job_dir, conn).await?;
    tracing::debug!("Finished preparing wrapper");

    let preprocessor = if let Some(pre_spread) = pre_spread {
        format!(
            r#"if inner_script.preprocessor is None or not callable(inner_script.preprocessor):
        raise ValueError("preprocessor function is missing")
    else:
        pre_args = {{}}
        {pre_spread}
        for k, v in list(pre_args.items()):
            if v == '<function call>':
                del pre_args[k]
        kwargs = inner_script.preprocessor(**pre_args)
        kwrags_json = res_to_json(kwargs, type(kwargs))
        with open("args.json", 'w') as f:
            f.write(kwrags_json)"#
        )
    } else {
        "".to_string()
    };

    let postprocessor = get_result_postprocessor(annotations.skip_result_postprocessing);

    let os_main_override = if let Some(main_override) = main_name.as_ref() {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Open the script and define a callable preprocessor function (the wrapper requires inner_script.preprocessor to be callable)
  2. Remove the preprocessor inputs/args from the job or step if preprocessing is no longer needed
  3. Re-save/redeploy the script so the deployed version matches the flow step's expectations
  4. Inspect '<function call>' placeholder handling: args whose value equals that marker are deleted, ensure you are not passing raw placeholders expecting a preprocessor to consume them

Example fix

# before: script has no preprocessor but step passes preprocessor args
# after: add to the script
def preprocessor(args):
    ...  # transform args
    return args
Defensive patterns

Strategy: validation

Validate before calling

# before deploying/running a job that uses preprocessor args
assert callable(getattr(script, 'preprocessor', None)), "script must define a callable preprocessor"

Type guard

def has_callable_preprocessor(script) -> bool:
    pre = getattr(script, 'preprocessor', None)
    return callable(pre)

Try / catch

try {
    runJob(payload)
} catch (e) {
    if (String(e).includes('preprocessor function is missing')) {
        // redeploy the script with a preprocessor or drop the preprocessor args
    } else { throw e }
}

Prevention

When it happens

Trigger: A job runs with preprocessor-related arguments (pre_spread present) but the deployed script's preprocessor field is None or not callable — e.g. the script was saved without a preprocessor, or its preprocessor was set to a non-function value.

Common situations: Editing a script and dropping the preprocessor definition while keeping preprocessor inputs; a preprocessor step removed/replaced upstream but the job payload still requests preprocessing; desync between script version and the flow step that passes preprocessor args.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/0fcd7d13ea301644. Report an issue: GitHub.