{"record":{"id":"36af4eb121e442dc","repo":"windmill-labs/windmill","slug":"what-must-be-a-non-empty-step-name-without","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":"Error","httpStatus":null,"severity":"error","filePath":"typescript-client/client.ts","lineNumber":1701,"sourceCode":") => Promise<Jsonified<Awaited<ReturnType<T>>>>;\n\nexport interface TaskOptions {\n  timeout?: number;\n  tag?: string;\n  cache_ttl?: number;\n  priority?: number;\n  concurrency_limit?: number;\n  concurrency_key?: string;\n  concurrency_time_window_s?: number;\n}\n\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 `waitForApproval` would\n *  accept a key `getApprovalUrls` can never address. */\nfunction assertUsableStepKey(key: string, what: string): void {\n  const k = key.trim();\n  if (k === \"\" || k === \".\" || k === \"..\" || key.includes(\"/\") || key.includes(\"\\\\\")) {\n    throw new Error(`${what} must be a non-empty step name without \\`/\\` or dot segments`);\n  }\n}\n\nexport let _workflowCtx: WorkflowCtx | null = null;\nexport function setWorkflowCtx(ctx: WorkflowCtx | null) {\n  _workflowCtx = ctx;\n  Reflect.set(globalThis, \"__wmill_wf_ctx\", ctx);\n}\n\n\nexport class WorkflowCtx {\n  private completed: Record<string, any>;\n  /** Null-prototype: step keys are caller-supplied, and a plain object would\n   *  resolve `toString`/`constructor`/`__proto__` off `Object.prototype`. */\n  private counters: Record<string, number> = Object.create(null);\n  /** Every key handed out by `_allocKey`, so distinct names can't alias one key. */\n  private _usedKeys = new Set<string>();\n  private pending: Array<{","sourceCodeStart":1683,"sourceCodeEnd":1719,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/typescript-client/client.ts#L1683-L1719","documentation":"assertUsableStepKey validates approval step keys because the key travels as a single URL path segment when getApprovalUrls mints resume/cancel URLs. An empty key, a key containing `/` or `\\\\`, or a dot segment (`.`/`..`) would produce URLs getApprovalUrls can never address, so the client rejects it up-front with this error naming the offending parameter via `what` (typescript-client/client.ts:1701).","triggerScenarios":"Passing an empty string, `\".\"`, `\"..\"`, or a key containing slashes/backslashes as the `key` option to waitForApproval, or as a step name wherever assertUsableStepKey runs.","commonSituations":"Building keys dynamically from user input or file paths (`approvals/2024-05`); interpolating empty template variables into the key; Windows-style backslash separators; copying a step path including slashes into the key field.","solutions":["Provide a non-empty key of path-segment-safe characters: letters, digits, `-`, `_`.","Sanitize dynamic keys before passing: strip or replace `/` and `\\\\`, reject dot-only values.","Encode hierarchy with a safe separator like `--` instead of `/`.","Add your own pre-call validation so bad keys fail before reaching the client."],"exampleFix":"// before\nawait waitForApproval({ key: `approvals/${ticketId}` }); // throws: contains '/'\n// after\nconst key = `approvals--${ticketId}`; // path-segment safe\nawait waitForApproval({ key });","handlingStrategy":"validation","validationCode":"function assertSafeKey(key: string, what = 'step key'): string {\n  const k = key.trim();\n  if (k === '' || k === '.' || k === '..' || key.includes('/') || key.includes('\\\\')) {\n    throw new Error(`${what} must be a non-empty path-segment-safe name`);\n  }\n  return k;\n}","typeGuard":"const isUsableStepKey = (k: unknown): k is string =>\n  typeof k === 'string' && k.trim() !== '' && !k.includes('/') && !k.includes('\\\\') && k !== '.' && k !== '..';","tryCatchPattern":"try {\n  await waitForApproval({ key });\n} catch (e) {\n  if (e.message.includes('must be a non-empty step name')) {\n    await waitForApproval({ key: sanitizeKey(key) });\n  } else throw e;\n}","preventionTips":["Validate keys before passing: no slashes, backslashes, dot segments, or empties.","Derive keys from safe ids, not user input or file paths.","Encode hierarchy with a separator like '--' instead of '/'."],"tags":["validation","approval","path"],"backgroundTag":"invalid-identifier","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"}