medusajs/medusa · error · Error

Unable to soft delete the ${relation.name}. Circular depende

Error message

Unable to soft delete the ${relation.name}. Circular dependency detected: ${circularDependencyStr}

What it means

detectCircularDependency walks the entity relation graph before recursively soft-deleting related records and throws this plain Error when it finds a cycle (A -> B -> ... -> A) that cannot be safely ordered for deletion. It is raised from mikro-orm/utils.ts during mikroOrmUpdateDeletedAtRecursively, which is used by repository soft-delete cascades.

Source

Thrown at packages/core/utils/src/dal/mikro-orm/utils.ts:41

  )

  for (const relation of relationsToCascade) {
    const branchVisited = new Set(Array.from(visited))

    const relationEntity =
      typeof relation.entity === "function"
        ? relation.entity()
        : relation.entity
    const isSelfCircularDependency = isString(relationEntity)
      ? entityMetadata.className === relationEntity
      : entityMetadata.class === relationEntity

    if (!isSelfCircularDependency && branchVisited.has(relation.name)) {
      const dependencies = Array.from(visited)
      dependencies.push(entityMetadata.className)
      const circularDependencyStr = dependencies.join(" -> ")

      throw new Error(
        `Unable to soft delete the ${relation.name}. Circular dependency detected: ${circularDependencyStr}`
      )
    }
    branchVisited.add(relation.name)

    const relationEntityMetadata = manager
      .getDriver()
      .getMetadata()
      .get(relation.type)

    detectCircularDependency(
      manager,
      relationEntityMetadata,
      branchVisited,
      isSelfCircularDependency
    )
  }
}

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Inspect the chain printed in the message (A -> B -> A) and remove one direction from the soft-deletable/cascade configuration so the graph becomes acyclic.
  2. Soft-delete each side explicitly in separate ordered service calls instead of relying on recursive cascade.
  3. If the inverse relation is only needed for querying, keep it on the model but exclude it from the soft-delete cascade configuration.
  4. After changing the model, regenerate the module migration if the change affects the schema.

Example fix

// before (module model link config — both directions soft-deletable)
softDelete: () => true // on A.b AND on B.a  => cycle

// after
// A.b keeps cascade:
//   b: one({ entity: () => B, ... })
// B.a becomes a plain relation without soft-delete cascade (or is soft-deleted manually after A)
Defensive patterns

Strategy: fallback

Try / catch

try {
  await service.softDelete(ids)
} catch (err) {
  if (err instanceof Error && /Circular dependency detected/.test(err.message)) {
    // fallback: soft-delete each side explicitly in a safe order
    for (const id of ids) await service.softDelete(id, { relations: acyclicSubset })
  } else throw err
}

Prevention

When it happens

Trigger: Calling softDelete on an entity whose softDeletable relation chain forms a cycle — e.g. entity A has a soft-deletable many-to-one to B while B also has a soft-deletable relation back to A; custom modules that add reciprocal soft-deletable relations between two models.

Common situations: Adding a new relation to a module data model and marking it soft-deletable without checking the inverse side already cascades back; customization that links e.g. order <-> fulfillment-ish tables in both directions with cascade soft deletes; version upgrades where new relations made an existing pair cyclic.

Related errors


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