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
- Always use the idempotency key exactly as handed to your step (it encodes the correct actionType)
- When handling async callbacks, catch this and treat late/incorrect-lifecycle responses as no-ops (log and ack)
- 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
- Always persist and reuse the exact idempotency key emitted for a step invocation
- Design async callbacks to tolerate late responses after rollback begins
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
- Cannot revert a permanent failed transaction.
- Action not found.
- Cannot skip a step when status is ${step.getStates().status}
- Cannot retry step when status is ${step.getStates().status}
- Method 'clearTransactionTimeout' not implemented.
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/a9dfc8b165da7493.
Report an issue: GitHub.