windmill-labs/windmill · error · ValueError

No @workflow function found in script

Error message

No @workflow function found in script

What it means

Raised as a Python ValueError inside the workflow-as-code runner injected by handle_python_job: after loading the user's inner script as a module, the runner scans it for a callable tagged `_is_workflow` (set by the @workflow decorator) and none exists, so there is no entry point to execute. It fires inside the sandboxed wrapper at job run time.

Source

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

    checkpoint = json.load(f, strict=False)

result_json = os.path.join(os.path.abspath(os.path.dirname(__file__)), "result.json")

try:
    {wac_preprocessor}
    args = kwargs
    for k, v in list(args.items()):
        if v == '<function call>':
            del args[k]

    workflow_fn = None
    for name in dir(inner_script):
        obj = getattr(inner_script, name)
        if callable(obj) and getattr(obj, '_is_workflow', False):
            workflow_fn = obj
            break
    if workflow_fn is None:
        raise ValueError("No @workflow function found in script")

    output = _run_workflow(workflow_fn, checkpoint, args)
    if isinstance(output, dict) and output.get("type") == "complete":
        print("")
        print("--- WAC: complete ---")
    output_json = json.dumps(output, separators=(',', ':'), default=str)
    with open(result_json, 'w') as f:
        f.write(output_json)
except BaseException as e:
    exc_type, exc_value, exc_traceback = sys.exc_info()
    tb = traceback.format_tb(exc_traceback)
    with open(result_json, 'w') as f:
        err = {{ "message": str(e), "name": e.__class__.__name__, "stack": '\n'.join(tb[1:]) }}
        extra = e.__dict__
        if extra and len(extra) > 0:
            err['extra'] = extra
        flow_node_id = os.environ.get('WM_FLOW_STEP_ID')
        if flow_node_id:

View on GitHub (pinned to e474e8803c)

Solutions

  1. Decorate the entry function with @workflow so the runner can find it
  2. Ensure the decorated function is defined at module top level, not nested or conditionally defined
  3. Check that no typo in the decorator import silently left the marker unset
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at backend/windmill-worker/src/python_executor.rs:900 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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