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
- Load fresh transaction state and verify the target step's status is failed (and the flow is in a retryable state) before calling retry
- Disable the retry action in UI/logic unless status === 'failed'
- 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
- Gate retry buttons on live step status === failed
- Restart the workflow instead of retrying when the flow is already compensating
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
- Cannot revert a permanent failed transaction.
- Incorrect action type.
- Cannot skip a step when status is ${step.getStates().status}
- Method 'clearTransactionTimeout' not implemented.
- Method 'scheduleStepTimeout' not implemented.
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/c5954adc4dfc0c14.
Report an issue: GitHub.