medusajs/medusa · error · MedusaError

Cannot retry step when status is ${step.getStates().status}

Error message

Cannot retry step when status is ${step.getStates().status}

What it means

retryStep/retry APIs re-run a previously failed step. A retry is only meaningful when the step is in a failed/idle-retryable state; retrying a step that is currently invoking, already succeeded, or whose state otherwise doesn't allow it throws MedusaError NOT_ALLOWED with the actual status included.

Source

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

    const [curTransaction, step] =
      await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(
        responseIdempotencyKey,
        handler,
        transaction
      )

    if (onLoad) {
      await onLoad(curTransaction)
    }

    if (step.getStates().status === TransactionStepStatus.WAITING) {
      this.emit(DistributedTransactionEvent.RESUME, {
        transaction: curTransaction,
      })

      await TransactionOrchestrator.retryStep(curTransaction, step)
    } else {
      throw new MedusaError(
        MedusaError.Types.NOT_ALLOWED,
        `Cannot retry step when status is ${step.getStates().status}`
      )
    }

    return curTransaction
  }

  /** Register a step success for 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
   * @param response - The response of the step
   */
  public async registerStepSuccess({
    responseIdempotencyKey,
    handler,
    transaction,

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Load fresh transaction state and verify the target step's status is failed (and the flow is in a retryable state) before calling retry
  2. Disable the retry action in UI/logic unless status === 'failed'
  3. For flows already compensating, restart the workflow instead of retrying a step

Example fix

// before
await orchestrator.retry(transactionId, action)

// after
const [, step] = await orchestrator.getTransactionStep(transactionId, action)
if (step.getStates().status !== 'failed') {
  return { retried: false, status: step.getStates().status }
}
await orchestrator.retry(transactionId, action)
Defensive patterns

Strategy: validation

Validate before calling

const [, step] = await orchestrator.getTransactionStep(transactionId, action)
if (step.getStates().status === 'failed') {
  await orchestrator.retry(transactionId, action)
} else {
  return { retried: false, status: step.getStates().status }
}

Try / catch

try { await orchestrator.retry(transactionId, action) } catch (e) { if (e.type === 'not_allowed' && /Cannot retry/.test(e.message)) return { retried: false } throw e }

Prevention

When it happens

Trigger: Calling retry on a step whose getStates().status is not failed (e.g. it is still running, was skipped, or already completed), or double-retrying after a retry already moved the step back to invocation.

Common situations: Admin 'retry' buttons acting on stale step state; racing a retry with the step finishing; retrying steps in workflows that already transitioned to compensating/failed permanently.

Related errors


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