medusajs/medusa · error · MedusaError
Transaction ${transactionId} could not be found.
Error message
Transaction ${transactionId} could not be found. What it means
TransactionOrchestrator.loadTransactionById (used by resume/cancel/retry paths) retrieves the persisted transaction by model id and transaction id from storage. If storage returns nothing, NOT_FOUND is thrown — the transaction was never checkpointed, was already cleaned up, or the ids are wrong.
Source
Thrown at packages/core/orchestration/src/transaction/transaction-orchestrator.ts:1786
/** Returns an existing transaction
* @param transactionId - unique identifier of the transaction
* @param handler - function to handle action of the transaction
*/
public async retrieveExistingTransaction(
transactionId: string,
handler: TransactionStepHandler,
options?: { isCancelling?: boolean }
): Promise<DistributedTransactionType> {
const existingTransaction =
await TransactionOrchestrator.loadTransactionById(
this.id,
transactionId,
{ isCancelling: options?.isCancelling }
)
if (!existingTransaction) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Transaction ${transactionId} could not be found.`
)
}
const transaction = new DistributedTransaction(
existingTransaction.flow,
handler,
undefined,
existingTransaction?.errors,
existingTransaction?.context
)
return transaction
}
private static getStepByAction(
flow: TransactionFlow,View on GitHub (pinned to 5e06e544a2)
Solutions
- Verify the transactionId and modelId exactly match what was returned when the workflow started
- Ensure the same workflow storage backend/instance is used across restarts (persistent Redis/DB, not memory)
- Check retention: if executions expire or clearExpiredExecutions purged them, the transaction is gone — restart the workflow instead of resuming
Example fix
// before
await orchestrator.resume(txId) // throws if never persisted
// after
const existing = await orchestrator.retrieveExistingTransaction(txId).catch(() => null)
if (!existing) {
// start fresh instead of resuming
await orchestrator.run(...)
} else {
await orchestrator.resume(existing)
} Defensive patterns
Strategy: try-catch
Try / catch
try { await orchestrator.resume(txId) } catch (e) { if (e.type === 'not_found' && /could not be found/.test(e.message)) { await orchestrator.run(/* fresh */) } else throw e } Prevention
- Use persistent workflow storage (Redis/DB) so transactions survive restarts
- Persist the transactionId returned at workflow start before attempting resume
When it happens
Trigger: Calling resume/cancel/skip with a transactionId that does not exist in the configured workflow storage, or after the execution record expired/was purged, or when the storage is empty (wrong Redis DB / different storage configured than the one that ran the workflow).
Common situations: Restarting the app with a fresh/in-memory workflow storage and trying to resume pre-restart transactions; pointing at a different Redis/database than where the workflow ran; typo'd or truncated transactionId (URL decoding issues).
Related errors
- Method 'clearTransactionTimeout' not implemented.
- Method 'scheduleStepTimeout' not implemented.
- Method 'clearStepTimeout' not implemented.
- Method 'clearExpiredExecutions' not implemented.
- Unable to serialize context object. Please make sure the wor
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/bdf192b41af66735.
Report an issue: GitHub.