medusajs/medusa · error · MedusaError

Cannot revert a permanent failed transaction.

Error message

Cannot revert a permanent failed transaction.

What it means

cancelTransaction (which internally handles revert semantics) refuses to operate on a transaction whose flow state is FAILED — a permanent failure. Once the flow itself has failed (not just a step), there is nothing to cancel/revert and the engine throws NOT_ALLOWED.

Solutions

  1. Check transaction.getFlow().state before cancelling and surface 'already permanently failed' to the user
  2. Fetch fresh transaction state right before cancelling to avoid stale-state races
  3. If you need cleanup after permanent failure, handle it via compensation/failure handlers rather than cancel

Example fix

// before
await orchestrator.cancelTransaction(tx)

// after
if (tx.getFlow().state === "failed") {
  return { alreadyFailed: true }
}
await orchestrator.cancelTransaction(tx)
Defensive patterns

Strategy: validation

Validate before calling

const { state } = tx.getFlow()
if (state === 'failed') {
  // nothing to cancel; report to caller
  return { alreadyFailed: true }
}

Try / catch

try { await orchestrator.cancelTransaction(tx) } catch (e) { if (e.type === 'not_allowed' && /permanent failed/.test(e.message)) return { ok: true, reason: 'already-failed' } throw e }

Prevention

When it happens

Trigger: Calling cancelTransaction/revert on a DistributedTransaction whose getFlow().state === TransactionState.FAILED, i.e. after the workflow definitively failed (e.g. a step failed with maxRetries exceeded and no compensation possible or already completed failure handling).

Common situations: Retry/cancel UI logic that operates on stale transaction state fetched before the flow finished failing; race where the transaction fails while a cancel request is in flight.

Related errors


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

Appendix: source

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

  /**
   * Cancel and revert a transaction compensating all its executed steps. It can be an ongoing transaction or a completed one
   * @param transaction - The transaction to be reverted
   */
  public async cancelTransaction(
    transaction: DistributedTransactionType,
    options?: { preventExecuteNext?: boolean }
  ): Promise<void> {
    if (transaction.modelId !== this.id) {
      throw new MedusaError(
        MedusaError.Types.NOT_ALLOWED,
        `TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.`
      )
    }

    const flow = transaction.getFlow()
    if (flow.state === TransactionState.FAILED) {
      throw new MedusaError(
        MedusaError.Types.NOT_ALLOWED,
        `Cannot revert a permanent failed transaction.`
      )
    }

    if (
      flow.state === TransactionState.COMPENSATING ||
      flow.state === TransactionState.WAITING_TO_COMPENSATE
    ) {
      throw new MedusaError(
        MedusaError.Types.NOT_ALLOWED,
        `Cannot revert a transaction that is already compensating.`
      )
    }

    flow.state = TransactionState.WAITING_TO_COMPENSATE
    flow.cancelledAt = Date.now()

View on GitHub (pinned to 5e06e544a2)