{"record":{"id":"b6380cc7fd32ac38","repo":"windmill-labs/windmill","slug":"wac-step-key-options-key-is-already-used-in-t","errorCode":null,"errorMessage":"WAC step key \"${options.key}\" is already used in this workflow. Give each waitForApproval() its own key so getApprovalUrls() can address it.","messagePattern":"WAC step key \"(.+?)\" is already used in this workflow\\. Give each waitForApproval\\(\\) its own key so getApprovalUrls\\(\\) can address it\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"typescript-client/client.ts","lineNumber":1856,"sourceCode":"    return steps;\n  }\n\n  _waitForApproval(options?: {\n    timeout?: number;\n    form?: object;\n    selfApproval?: boolean;\n    key?: string;\n  }): PromiseLike<{ value: any; approver: string; approved: boolean }> {\n    this._rethrowSwallowed();\n    if (options?.key !== undefined) assertUsableStepKey(options.key, \"waitForApproval key\");\n    const key = this._allocKey(options?.key || \"approval\");\n\n    // An explicit key is an identifier callers mint URLs against, so silently\n    // renaming a duplicate to `<key>_2` would hand them a URL for the *first*\n    // step — which then fails with \"resume request already sent\" and parks the\n    // workflow until timeout. Unnamed approvals keep auto-numbering.\n    if (options?.key && key !== options.key) {\n      throw new Error(\n        `WAC step key \"${options.key}\" is already used in this workflow. ` +\n          `Give each waitForApproval() its own key so getApprovalUrls() can address it.`,\n      );\n    }\n\n    if (key in this.completed) {\n      const value = this.completed[key];\n      return { then: (resolve: any) => resolve(value) };\n    }\n\n    // In child job mode, return never-resolving thenable (same as _nextStep)\n    if (this._executingKey !== null) {\n      return { then: () => new Promise(() => {}) };\n    }\n\n    // Throw immediately — approval is always a blocking step\n    console.log(`\\n--- WAC: approval(${key}) ---`);\n    this._raiseSuspend({","sourceCodeStart":1838,"sourceCodeEnd":1874,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/typescript-client/client.ts#L1838-L1874","documentation":"Within one workflow, each waitForApproval must have a distinct explicit key. When a duplicate is detected, the client refuses to silently rename it to `<key>_2` because the caller would then hold resume URLs pointing at the *first* step, which fail with 'resume request already sent' and park the workflow until timeout. The error names the duplicated key and asks for unique keys per step (typescript-client/client.ts:1856).","triggerScenarios":"Calling waitForApproval({ key: \"ship-it\" }) twice in the same workflow, or in a loop whose iterations reuse the same literal key.","commonSituations":"Copy-pasting an approval step and forgetting to change the key; loops creating one approval per item but hard-coding the key; dynamic branches generating approvals with colliding keys.","solutions":["Give each waitForApproval a unique key (suffix with loop index, item id, or step purpose).","Omit the `key` option entirely to keep auto-numbering when uniqueness is not needed.","In loops, build the key from the iteration variable: `waitForApproval({ key: `approve-${i}` })`.","Audit the workflow for repeated `key:` literals."],"exampleFix":"// before\nfor (const item of items) {\n  await waitForApproval({ key: 'approve-item' }); // duplicate on 2nd iteration\n}\n// after\nfor (const [i, item] of items.entries()) {\n  await waitForApproval({ key: `approve-item-${i}` });\n}","handlingStrategy":"validation","validationCode":"function assertUniqueApprovalKey(key: string, seen: Set<string>) {\n  if (seen.has(key)) throw new Error(`duplicate approval key: ${key}`);\n  seen.add(key);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await waitForApproval({ key: 'approve-item' });\n} catch (e) {\n  if (e.message.includes('is already used in this workflow')) {\n    await waitForApproval({ key: 'approve-item-' + crypto.randomUUID() });\n  } else throw e;\n}","preventionTips":["Never hard-code the same key literal in loops or copied steps.","Omit `key` to let the client auto-number when uniqueness is not needed.","Keep a set of used keys while building the workflow and assert uniqueness."],"tags":["approval","workflow","duplicate-key"],"backgroundTag":"duplicate-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"}