medusajs/medusa · error · Error

Cannot create role parent relationship: this would create a

Error message

Cannot create role parent relationship: this would create a circular dependency (role_id: ${role_id}, parent_id: ${parent_id})

What it means

Thrown by the RBAC module when creating a role-parent relationship that would introduce a cycle in the role hierarchy (e.g. A parent of B and B parent of A). checkForCycle runs against the repository before insert, so the transaction never persists a cyclic graph.

Source

Thrown at packages/modules/rbac/src/services/rbac-module-service.ts:262

    @MedusaContext() sharedContext: Context = {}
  ): Promise<RbacRoleParentDTO[]> {
    for (const parent of data) {
      const { role_id, parent_id } = parent

      if (role_id === parent_id) {
        throw new Error(
          `Cannot create role parent relationship: a role cannot be its own parent (role_id: ${role_id})`
        )
      }

      const wouldCreateCycle = await this.rbacRepository_.checkForCycle(
        role_id,
        parent_id,
        sharedContext
      )

      if (wouldCreateCycle) {
        throw new Error(
          `Cannot create role parent relationship: this would create a circular dependency (role_id: ${role_id}, parent_id: ${parent_id})`
        )
      }
    }

    return await super.createRbacRoleParents(data, sharedContext)
  }

  @InjectManager()
  // @ts-expect-error
  async updateRbacRoleParents(
    data: UpdateRbacRoleParentDTO[],
    @MedusaContext() sharedContext: Context = {}
  ): Promise<RbacRoleParentDTO[]> {
    for (const parent of data) {
      const { role_id, parent_id } = parent

      if (parent_id) {

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Reorder hierarchy imports so parents are always created before children (topological order)
  2. Pick a different parent that is not a descendant of the current role
  3. Break the existing cycle first by removing or re-pointing the offending relation, then retry the create

Example fix

// before
// B already has parent A
await rbacService.createRbacRoleParents([
  { role_id: a, parent_id: b }, // creates A -> B -> A cycle
])
// after
await rbacService.createRbacRoleParents([
  { role_id: c, parent_id: b }, // leaf role instead
])
Defensive patterns

Strategy: validation

Validate before calling

async function wouldCycle(rbac: any, roleId: string, parentId: string) {
  // walk up from parentId; if we reach roleId, it's a cycle
  const seen = new Set<string>()
  let current = parentId
  while (current && !seen.has(current)) {
    if (current === roleId) return true
    seen.add(current)
    const parents = await rbac.listRbacRoleParents({ role_id: current })
    current = parents[0]?.parent_id
  }
  return false
}

Type guard

async function isAcyclicParent(rbac: any, rel: { role_id: string; parent_id: string }): Promise<boolean> {
  return rel.role_id !== rel.parent_id && !(await wouldCycle(rbac, rel.role_id, rel.parent_id))
}

Try / catch

try {
  await rbacService.createRbacRoleParents(payload)
} catch (e) {
  if (/circular dependency/.test(e.message)) {
    // skip the offending relation and continue with the rest
    continueImport()
  } else throw e
}

Prevention

When it happens

Trigger: Calling createRbacRoleParents where the proposed parent already has the current role as an ancestor — for example setting role A's parent to B after B's parent was set to A — including via setRoleParentStep or the createRbacRoleParents workflow.

Common situations: Bulk-importing role hierarchies from flat data where ordering creates temporary cycles, concurrent updates that each look acyclic individually, or admin UI allowing arbitrary parent selection without cycle checks.

Related errors


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