medusajs/medusa · error · Error
Cannot update role parent relationship: this would create a
Error message
Cannot update 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 an update to a role-parent relationship would create a circular dependency in the hierarchy. checkForCycle is evaluated with the proposed role_id/parent_id before the update is persisted.
Source
Thrown at packages/modules/rbac/src/services/rbac-module-service.ts:294
): Promise<RbacRoleParentDTO[]> {
for (const parent of data) {
const { role_id, parent_id } = parent
if (parent_id) {
if (role_id === parent_id) {
throw new Error(
`Cannot update 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 update role parent relationship: this would create a circular dependency (role_id: ${role_id}, parent_id: ${parent_id})`
)
}
}
}
return await super.updateRbacRoleParents(data, sharedContext)
}
}
View on GitHub (pinned to 5e06e544a2)
Solutions
- Choose a new parent outside the current role's descendant chain
- If restructuring a whole subtree, re-point children first (bottom-up) so no intermediate state is cyclic
- Verify the intended tree shape in a dry-run/topological pass before applying updates
Example fix
// before
// existing: A parent of B
await rbacService.updateRbacRoleParents([
{ id: bRelationId, role_id: b, parent_id: a }, // cycle
])
// after
await rbacService.updateRbacRoleParents([
{ id: bRelationId, role_id: b, parent_id: rootRole },
]) Defensive patterns
Strategy: validation
Validate before calling
async function assertNoUpdateCycle(rbac: any, u: { role_id: string; parent_id: string }) {
const seen = new Set<string>()
let current = u.parent_id
while (current && !seen.has(current)) {
if (current === u.role_id) throw new Error('Update would create a cycle')
seen.add(current)
const parents = await rbac.listRbacRoleParents({ role_id: current })
current = parents[0]?.parent_id
}
} Type guard
async function isNonCyclicUpdate(rbac: any, u: { role_id: string; parent_id: string }): Promise<boolean> {
const seen = new Set<string>()
let cur = u.parent_id
while (cur && !seen.has(cur)) {
if (cur === u.role_id) return false
seen.add(cur)
cur = (await rbac.listRbacRoleParents({ role_id: cur }))[0]?.parent_id
}
return true
} Try / catch
try {
await rbacService.updateRbacRoleParents(updates)
} catch (e) {
if (/circular dependency/.test(e.message)) {
// alert: re-parenting attempted under own subtree; pick another parent
} else throw e
} Prevention
- Restructure subtrees bottom-up so intermediate states stay acyclic
- Preview re-parent operations against the current tree before applying
- Lock hierarchy edits during bulk reorganizations
When it happens
Trigger: Calling updateRbacRoleParents where the new parent_id is a descendant of role_id — e.g. after A->B exists, updating B's relation to point at A.
Common situations: Re-parenting operations that move a subtree under one of its own descendants, hierarchy imports run as updates, or races where two concurrent re-parents each pass the check individually.
Related errors
- Cannot create role parent relationship: this would create a
- Cannot update role parent relationship: a role cannot be its
- Role with id: ${req.params.id} not found
- Cannot create role parent relationship: a role cannot be its
- Forbidden
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/b4f65e0039078068.
Report an issue: GitHub.