medusajs/medusa · error · Error

Incorrect action type.

Error message

Incorrect action type.

What it means

A responseIdempotencyKey's last segment encodes the handler type (invoke vs compensate). The orchestrator checks it against the step's current mode: a compensating step must receive COMPENSATE responses, an invoking step INVOKE. A mismatch throws this plain Error, protecting step state from wrong-lifecycle updates.

Source

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

        undefined,
        existingTransaction.errors,
        existingTransaction.context
      )
    }

    const step = TransactionOrchestrator.getStepByAction(
      transaction.getFlow(),
      action
    )

    if (step === null) {
      throw new Error("Action not found.")
    } else if (
      step.isCompensating()
        ? actionType !== TransactionHandlerType.COMPENSATE
        : actionType !== TransactionHandlerType.INVOKE
    ) {
      throw new Error("Incorrect action type.")
    }
    return [transaction, step]
  }

  /** Skip the execution of a specific transaction and step
   * @param responseIdempotencyKey - The idempotency key for the step
   * @param handler - The handler function to execute the step
   * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
   */
  public async skipStep({
    responseIdempotencyKey,
    handler,
    transaction,
  }: {
    responseIdempotencyKey: string
    handler?: TransactionStepHandler
    transaction?: DistributedTransactionType
  }): Promise<DistributedTransactionType> {

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Always use the idempotency key exactly as handed to your step (it encodes the correct actionType)
  2. When handling async callbacks, catch this and treat late/incorrect-lifecycle responses as no-ops (log and ack)
  3. If a response legitimately arrives during compensation, respond through the compensation path or re-run the workflow

Example fix

// before
const key = `${modelId}:${txId}:${action}:invoke` // hardcoded type

// after
// echo back the key the engine generated for this step invocation
await orchestrator.transactionStepResponse(step.context.idempotencyKey)
Defensive patterns

Strategy: try-catch

Validate before calling

const [, , action, actionType] = key.split(':')
const expected = step.isCompensating() ? 'compensate' : 'invoke'
if (actionType !== expected) {
  return // late/out-of-lifecycle response; ignore
}

Try / catch

try { await orchestrator.transactionStepResponse(key, null, handler) } catch (e) { if (/Incorrect action type/.test(e.message)) { /* late async response during compensation; ack */ return } throw e }

Prevention

When it happens

Trigger: Submitting a response whose key ends in :invoke for a step that is currently compensating (or :compensate for an invoking step). Typically from reusing/deriving keys manually, or an async response arriving after the flow already began compensating.

Common situations: Webhook for an async step arriving after the workflow started rolling back; constructing keys from the wrong actionType constant; duplicated callback processing with stale keys.

Related errors


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