{"record":{"id":"a9dfc8b165da7493","repo":"medusajs/medusa","slug":"incorrect-action-type","errorCode":null,"errorMessage":"Incorrect action type.","messagePattern":"Incorrect action type\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/orchestration/src/transaction/transaction-orchestrator.ts","lineNumber":1864,"sourceCode":"        undefined,\n        existingTransaction.errors,\n        existingTransaction.context\n      )\n    }\n\n    const step = TransactionOrchestrator.getStepByAction(\n      transaction.getFlow(),\n      action\n    )\n\n    if (step === null) {\n      throw new Error(\"Action not found.\")\n    } else if (\n      step.isCompensating()\n        ? actionType !== TransactionHandlerType.COMPENSATE\n        : actionType !== TransactionHandlerType.INVOKE\n    ) {\n      throw new Error(\"Incorrect action type.\")\n    }\n    return [transaction, step]\n  }\n\n  /** Skip the execution of a specific transaction and step\n   * @param responseIdempotencyKey - The idempotency key for the step\n   * @param handler - The handler function to execute the step\n   * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey\n   */\n  public async skipStep({\n    responseIdempotencyKey,\n    handler,\n    transaction,\n  }: {\n    responseIdempotencyKey: string\n    handler?: TransactionStepHandler\n    transaction?: DistributedTransactionType\n  }): Promise<DistributedTransactionType> {","sourceCodeStart":1846,"sourceCodeEnd":1882,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/core/orchestration/src/transaction/transaction-orchestrator.ts#L1846-L1882","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst key = `${modelId}:${txId}:${action}:invoke` // hardcoded type\n\n// after\n// echo back the key the engine generated for this step invocation\nawait orchestrator.transactionStepResponse(step.context.idempotencyKey)","handlingStrategy":"try-catch","validationCode":"const [, , action, actionType] = key.split(':')\nconst expected = step.isCompensating() ? 'compensate' : 'invoke'\nif (actionType !== expected) {\n  return // late/out-of-lifecycle response; ignore\n}","typeGuard":null,"tryCatchPattern":"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 }","preventionTips":["Always persist and reuse the exact idempotency key emitted for a step invocation","Design async callbacks to tolerate late responses after rollback begins"],"tags":["orchestration","workflow","idempotency-key","invalid-state"],"backgroundTag":"invalid-workflow-state-transition","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}