{"record":{"id":"8e65b8fd7fa3d3a9","repo":"windmill-labs/windmill","slug":"what-must-be-a-non-empty-step-name-without-o","errorCode":null,"errorMessage":"{what} must be a non-empty step name without `/` or dot segments","messagePattern":"(.+?) must be a non-empty step name without `/` or dot segments","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python-client/wmill/wmill/client.py","lineNumber":2717,"sourceCode":"        name = \"main\"\n    return name, schema\n\n\n# ── Workflow-as-Code SDK ──────────────────────────────────────────────\n\nimport asyncio as _asyncio\nimport contextvars as _contextvars\nimport sys as _sys\nimport traceback as _traceback\n\n\ndef _assert_usable_step_key(key: str, what: str) -> None:\n    \"\"\"A step key travels as one path segment when its URLs are minted, so it must be\n    non-empty and free of ``/`` and dot segments — otherwise ``wait_for_approval``\n    would accept a key ``get_approval_urls`` can never address.\"\"\"\n    k = key.strip()\n    if not k or k in (\".\", \"..\") or \"/\" in key or \"\\\\\" in key:\n        raise RuntimeError(f\"{what} must be a non-empty step name without `/` or dot segments\")\n\n\nclass _StepSuspend(BaseException):\n    \"\"\"Raised to suspend workflow execution. Inherits from BaseException\n    so it is not caught by bare `except Exception:` blocks.\"\"\"\n\n    def __init__(self, dispatch_info: dict):\n        self.dispatch_info = dispatch_info\n\n\nclass _StepFailure(BaseException):\n    \"\"\"Carries the exception raised by the step a child round executes directly.\n\n    That exception *is* the round's result, so a broad ``except Exception`` in the\n    body must not be able to turn it into a successful complete — the parent would\n    then record the caught branch's value as the step result. BaseException for the\n    same reason ``_StepSuspend`` is; a bare ``except:`` still swallows both.\n    \"\"\"","sourceCodeStart":2699,"sourceCodeEnd":2735,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/python-client/wmill/wmill/client.py#L2699-L2735","documentation":"_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.","triggerScenarios":"Calling wait_for_approval(key='') or whitespace-only, key='a/b', key='..', key='.', or keys containing backslashes.","commonSituations":"Dynamically building keys from user input or resource names containing slashes; interpolating an empty variable into key; pasting a path segment as a key.","solutions":["Use a simple identifier: letters, digits, dashes, underscores (e.g. 'manager-approval')","Sanitize before passing: replace '/' and '\\\\' with '-', strip whitespace","Pre-validate with the same rule: k and k not in ('.','..') and '/' not in k and '\\\\' not in k","Encode hierarchy in keys with a safe separator (e.g. '--') instead of '/'"],"exampleFix":"// before\nawait client.wait_for_approval(key=f\"approvals/{step_id}\")\n// after\nawait client.wait_for_approval(key=f\"approvals--{step_id}\")","handlingStrategy":"validation","validationCode":"import re\ndef valid_step_key(k: str) -> str:\n    k2 = k.strip()\n    if not k2 or k2 in (\".\", \"..\") or \"/\" in k or \"\\\\\" in k:\n        raise ValueError(f\"invalid step key: {k!r}\")\n    return k2","typeGuard":null,"tryCatchPattern":"try:\n    await client.wait_for_approval(key=k)\nexcept RuntimeError as e:\n    if \"must be a non-empty step name\" in str(e):\n        k = re.sub(r\"[/\\\\.]\", \"-\", k).strip(\"-\") or \"approval\"\n        await client.wait_for_approval(key=k)\n    else:\n        raise","preventionTips":["Use only [A-Za-z0-9_-] characters in step keys","Sanitize dynamic keys derived from user input or resource names","Never interpolate paths or URLs as keys","Keep a shared key-normalization helper for all wait_for_approval call sites"],"tags":["validation","approval","wac","key-format"],"backgroundTag":"invalid-identifier-format","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}