medusajs/medusa · error · MedusaError

TransactionModel "${transaction.modelId}" cannot be orchestr

Error message

TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.

What it means

TransactionOrchestrator.resume(transaction) can only resume transactions that belong to the same workflow model. Every DistributedTransaction carries the modelId of the flow definition it was created from; resuming with an orchestrator whose id differs throws NOT_ALLOWED to prevent applying the wrong flow to saved state.

Source

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

    if (ret.transactionIsCancelling) {
      await this.cancelTransaction(transaction, {
        preventExecuteNext: true,
      })
    }

    if (isAsync && !ret.stopExecution) {
      // Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
      await transaction.scheduleRetry(step, 0)
    }
  }

  /**
   * Start a new transaction or resume a transaction that has been previously started
   * @param transaction - The transaction to resume
   */
  public async resume(transaction: DistributedTransactionType): Promise<void> {
    if (transaction.modelId !== this.id) {
      throw new MedusaError(
        MedusaError.Types.NOT_ALLOWED,
        `TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.`
      )
    }

    if (transaction.hasFinished()) {
      return
    }

    const executeNext = async () => {
      const flow = transaction.getFlow()

      if (flow.state === TransactionState.NOT_STARTED) {
        flow.state = TransactionState.INVOKING
        flow.startedAt = Date.now()

        await transaction.saveCheckpoint({
          ttl: flow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL,

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Resume with the orchestrator whose workflow id matches transaction.modelId
  2. If a workflow was renamed, use the old orchestrator (or same id) to resume in-flight transactions, then migrate
  3. Filter by modelId when loading transactions from storage before resuming

Example fix

// before
await someOtherOrchestrator.resume(loadedTx)

// after
if (loadedTx.modelId === orchestrator.id) {
  await orchestrator.resume(loadedTx)
}
Defensive patterns

Strategy: type-guard

Type guard

const canResume = (orchestrator, tx) =>
  tx.modelId === orchestrator.id && !tx.hasFinished()

Try / catch

try { await orchestrator.resume(tx) } catch (e) { if (e.type === 'not_allowed' && /cannot be orchestrated/.test(e.message)) { /* route to correct orchestrator */ } throw e }

Prevention

When it happens

Trigger: Calling orchestratorA.resume(transactionB) where transactionB.modelId !== orchestratorA.id — e.g. loading a transaction from storage with one workflow's orchestrator and resuming it with another, or resuming after renaming a workflow's id.

Common situations: Renaming a workflow (its id) while old transactions still exist in storage; sharing a storage across multiple workflow engines and mixing up orchestrator instances; code that fetches 'any' transaction and resumes it generically.

Related errors


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