medusajs/medusa · error · Error

Step ${obj.action} is already defined in workflow.

Error message

Step ${obj.action} is already defined in workflow.

What it means

While flattening the workflow graph into executable step ids, the orchestrator records every action alias it visits. Encountering the same alias twice means two steps share one action name, which would break step addressing/idempotency keys — so it throws immediately at workflow definition time.

Source

Thrown at packages/core/orchestration/src/transaction/transaction-orchestrator.ts:1620

    const actionNames = new Set<string>()
    const queue: any[] = [
      { obj: flow, level: [TransactionOrchestrator.ROOT_STEP] },
    ]

    const features = {
      hasAsyncSteps: false,
      hasStepTimeouts: false,
      hasRetriesTimeout: false,
      hasNestedTransactions: false,
    }

    while (queue.length > 0) {
      const { obj, level } = queue.shift()

      if (obj.action) {
        if (actionNames.has(obj.action)) {
          throw new Error(`Step ${obj.action} is already defined in workflow.`)
        }

        actionNames.add(obj.action)
        level.push(obj.action)
        const id = level.join(".")
        const parent = level.slice(0, level.length - 1).join(".")

        if (!existingSteps || parent === TransactionOrchestrator.ROOT_STEP) {
          states[parent].next?.push(id)
        }

        const definitionCopy = { ...obj } as TransactionStepsDefinition
        delete definitionCopy.next

        const isAsync = !!definitionCopy.async
        const hasRetryInterval = !!(
          definitionCopy.retryInterval || definitionCopy.retryIntervalAwaiting
        )

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Give every step a unique alias/name within the workflow (pass distinct names to createStep or rename on compose)
  2. Do not reuse the same step node object in multiple positions of the graph; create separate instances
  3. When composing parallel branches, prefix sub-workflow step names (namespacing)

Example fix

// before
const sameStep = createStep('charge', ...)
createWorkflow('w', () => { sameStep(); sameStep() })

// after
const chargeA = createStep('charge-a', ...)
const chargeB = createStep('charge-b', ...)
createWorkflow('w', () => { chargeA(); chargeB() })
Defensive patterns

Strategy: validation

Validate before calling

const names = new Set()
for (const s of mySteps) {
  if (names.has(s.__action__)) throw new Error(`duplicate step ${s.__action__}`)
  names.add(s.__action__)
}

Prevention

When it happens

Trigger: Defining a workflow where the same step alias is inserted twice (e.g. appending a step object that is reused in two places, or two createStep calls that got the same generated/assigned alias via .invoke config reuse), or composing sub-workflows that both expose a step with the same name.

Common situations: Reusing a step instance variable in two parts of a flow; copy-pasting a step definition and forgetting to change its alias; merging parallel branches that contain identically named steps.

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/ac9248637bc43141. Report an issue: GitHub.