windmill-labs/windmill · error · RuntimeError

{what} must be a non-empty step name without `/` or dot segm

Error message

{what} must be a non-empty step name without `/` or dot segments

What it means

_assert_usable_step_key validates wait_for_approval step keys: a key travels as one path segment in minted approval URLs, so it must be non-empty and free of '/', backslashes, and dot segments ('.', '..'). Otherwise wait_for_approval would accept a key that get_approval_urls can never address. This RuntimeError fires at call time naming the offending key via the `what` prefix.

Source

Thrown at python-client/wmill/wmill/client.py:2717

        name = "main"
    return name, schema


# ── Workflow-as-Code SDK ──────────────────────────────────────────────

import asyncio as _asyncio
import contextvars as _contextvars
import sys as _sys
import traceback as _traceback


def _assert_usable_step_key(key: str, what: str) -> None:
    """A step key travels as one path segment when its URLs are minted, so it must be
    non-empty and free of ``/`` and dot segments — otherwise ``wait_for_approval``
    would accept a key ``get_approval_urls`` can never address."""
    k = key.strip()
    if not k or k in (".", "..") or "/" in key or "\\" in key:
        raise RuntimeError(f"{what} must be a non-empty step name without `/` or dot segments")


class _StepSuspend(BaseException):
    """Raised to suspend workflow execution. Inherits from BaseException
    so it is not caught by bare `except Exception:` blocks."""

    def __init__(self, dispatch_info: dict):
        self.dispatch_info = dispatch_info


class _StepFailure(BaseException):
    """Carries the exception raised by the step a child round executes directly.

    That exception *is* the round's result, so a broad ``except Exception`` in the
    body must not be able to turn it into a successful complete — the parent would
    then record the caught branch's value as the step result. BaseException for the
    same reason ``_StepSuspend`` is; a bare ``except:`` still swallows both.
    """

View on GitHub (pinned to e474e8803c)

Solutions

  1. Use a simple identifier: letters, digits, dashes, underscores (e.g. 'manager-approval')
  2. Sanitize before passing: replace '/' and '\\' with '-', strip whitespace
  3. Pre-validate with the same rule: k and k not in ('.','..') and '/' not in k and '\\' not in k
  4. Encode hierarchy in keys with a safe separator (e.g. '--') instead of '/'

Example fix

// before
await client.wait_for_approval(key=f"approvals/{step_id}")
// after
await client.wait_for_approval(key=f"approvals--{step_id}")
Defensive patterns

Strategy: validation

Validate before calling

import re
def valid_step_key(k: str) -> str:
    k2 = k.strip()
    if not k2 or k2 in (".", "..") or "/" in k or "\\" in k:
        raise ValueError(f"invalid step key: {k!r}")
    return k2

Try / catch

try:
    await client.wait_for_approval(key=k)
except RuntimeError as e:
    if "must be a non-empty step name" in str(e):
        k = re.sub(r"[/\\.]", "-", k).strip("-") or "approval"
        await client.wait_for_approval(key=k)
    else:
        raise

Prevention

When it happens

Trigger: Calling wait_for_approval(key='') or whitespace-only, key='a/b', key='..', key='.', or keys containing backslashes.

Common situations: Dynamically building keys from user input or resource names containing slashes; interpolating an empty variable into key; pasting a path segment as a key.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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