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
- Reorder hierarchy imports so parents are always created before children (topological order)
- Pick a different parent that is not a descendant of the current role
- 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
- Import hierarchies in topological order (parents first)
- Maintain an in-memory role tree during bulk operations and check descendants before linking
- Serialize hierarchy mutations behind a queue to avoid concurrent cycle misses
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
- Cannot create role parent relationship: a role cannot be its
- Cannot update role parent relationship: this would create a
- Role with id: ${req.params.id} not found
- Cannot update role parent relationship: a role cannot be its
- Forbidden
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/ca1584b9d74d5c66.
Report an issue: GitHub.