{"record":{"id":"c7d685bbbb524f26","repo":"medusajs/medusa","slug":"cannot-update-role-parent-relationship-a-role-can","errorCode":null,"errorMessage":"Cannot update role parent relationship: a role cannot be its own parent (role_id: ${role_id})","messagePattern":"Cannot update role parent relationship: a role cannot be its own parent \\(role_id: (.+?)\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/modules/rbac/src/services/rbac-module-service.ts","lineNumber":282,"sourceCode":"        )\n      }\n    }\n\n    return await super.createRbacRoleParents(data, sharedContext)\n  }\n\n  @InjectManager()\n  // @ts-expect-error\n  async updateRbacRoleParents(\n    data: UpdateRbacRoleParentDTO[],\n    @MedusaContext() sharedContext: Context = {}\n  ): Promise<RbacRoleParentDTO[]> {\n    for (const parent of data) {\n      const { role_id, parent_id } = parent\n\n      if (parent_id) {\n        if (role_id === parent_id) {\n          throw new Error(\n            `Cannot update role parent relationship: a role cannot be its own parent (role_id: ${role_id})`\n          )\n        }\n\n        const wouldCreateCycle = await this.rbacRepository_.checkForCycle(\n          role_id!,\n          parent_id,\n          sharedContext\n        )\n\n        if (wouldCreateCycle) {\n          throw new Error(\n            `Cannot update role parent relationship: this would create a circular dependency (role_id: ${role_id}, parent_id: ${parent_id})`\n          )\n        }\n      }\n    }\n","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/modules/rbac/src/services/rbac-module-service.ts#L264-L300","documentation":"Thrown by the RBAC module when updating an existing role-parent relationship if the update sets parent_id equal to role_id, making the role its own parent. It fires inside updateRbacRoleParents before any write, only when parent_id is provided in the update payload.","triggerScenarios":"Calling updateRbacRoleParents with { id, role_id: X, parent_id: X } — i.e. an update payload whose new parent equals the role being updated.","commonSituations":"Edit forms that submit the role itself as parent when the parent field is left blank but defaulted, or PATCH payloads echoing back role_id into parent_id.","solutions":["Send parent_id of a different role, or omit parent_id entirely if it should not change","To remove a parent, use the delete relation API rather than setting parent_id = role_id","Guard the edit form against submitting the current role as its own parent"],"exampleFix":"// before\nawait rbacService.updateRbacRoleParents([\n  { id: relId, role_id: roleId, parent_id: roleId },\n])\n// after\nawait rbacService.updateRbacRoleParents([\n  { id: relId, role_id: roleId, parent_id: otherRoleId },\n])","handlingStrategy":"validation","validationCode":"function assertUpdateNotSelfParent(updates: { role_id?: string; parent_id?: string }[]) {\n  for (const u of updates) {\n    if (u.parent_id && u.role_id && u.role_id === u.parent_id) {\n      throw new Error(`Update sets role ${u.role_id} as its own parent`)\n    }\n  }\n}","typeGuard":"function isSafeParentUpdate(u: { role_id?: string; parent_id?: string }): boolean {\n  return !u.parent_id || !u.role_id || u.role_id !== u.parent_id\n}","tryCatchPattern":"try {\n  await rbacService.updateRbacRoleParents(updates)\n} catch (e) {\n  if (/cannot be its own parent/.test(e.message)) {\n    updates = updates.filter(isSafeParentUpdate)\n    await rbacService.updateRbacRoleParents(updates)\n  } else throw e\n}","preventionTips":["Omit parent_id from PATCH payloads when unchanged","Map empty parent selection to a delete-relation call, not parent_id=role_id"],"tags":["rbac","roles","update","validation"],"backgroundTag":"self-referencing-relation","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}