{"record":{"id":"7e933b0f96de4fab","repo":"n8n-io/n8n","slug":"cannot-update-system-roles","errorCode":null,"errorMessage":"Cannot update system roles","messagePattern":"Cannot update system roles","errorType":"exception","errorClass":"UserError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/db/src/repositories/role.repository.ts","lineNumber":241,"sourceCode":"\t\t\t\tthrow new Error(`Failed to delete role \"${role.slug}\"`);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async updateEntityWithManager(\n\t\tentityManager: EntityManager,\n\t\tslug: string,\n\t\tnewData: Partial<Pick<Role, 'description' | 'scopes' | 'displayName'>>,\n\t) {\n\t\tconst role = await entityManager.findOne(Role, {\n\t\t\twhere: { slug },\n\t\t\trelations: ['scopes'],\n\t\t});\n\t\tif (!role) {\n\t\t\tthrow new UserError('Role not found');\n\t\t}\n\t\tif (role.systemRole) {\n\t\t\tthrow new UserError('Cannot update system roles');\n\t\t}\n\n\t\t// Only update fields that are explicitly provided (not undefined)\n\t\t// This preserves existing scopes when scopes is undefined\n\t\tif (newData.displayName !== undefined) {\n\t\t\trole.displayName = newData.displayName;\n\t\t}\n\n\t\tif (newData.description !== undefined) {\n\t\t\trole.description = newData.description;\n\t\t}\n\n\t\tif (newData.scopes !== undefined) {\n\t\t\trole.scopes = newData.scopes;\n\t\t}\n\n\t\treturn await entityManager.save<Role>(role);\n\t}","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/db/src/repositories/role.repository.ts#L223-L259","documentation":"UserError thrown by RoleRepository.updateEntityWithManager when the looked-up role has systemRole === true. System roles (e.g. admin, member, owner) are immutable by design — their scopes and metadata cannot be edited through the update path. This guard runs after the not-found check.","triggerScenarios":"Calling roleRepository.update(slug, ...) where slug identifies a system role (one flagged systemRole in the DB). Any attempt to change displayName, description, or scopes on a built-in role trips this.","commonSituations":"Admin tooling trying to tighten or extend a built-in role; a script iterating all roles and blindly updating each; confusion between system and custom roles; attempting to rename 'admin'.","solutions":["Do not modify system roles — if you need different scopes, create a custom role and assign users to it.","Filter system roles out before bulk operations: `if (!role.systemRole) await update(...)`.","Surface a clear UI message: 'Built-in roles cannot be edited; create a custom role instead.'","If policy truly requires changing a system role, treat it as a product change and ship a migration, not a runtime update."],"exampleFix":"// before\nfor (const slug of allSlugs) await roleRepo.update(slug, patch);\n\n// after\nfor (const role of await roleRepo.find()) {\n  if (role.systemRole) continue;\n  await roleRepo.update(role.slug, patch);\n}","handlingStrategy":"type-guard","validationCode":"const role = await roleRepository.findOneBy({ slug });\nif (!role) { /* not-found path */ }\nif (role.systemRole) { /* refuse to update; create a custom role instead */ }","typeGuard":"function isEditableRole(role: Role): boolean {\n  return !role.systemRole;\n}","tryCatchPattern":"try {\n  await roleRepository.update(slug, patch);\n} catch (err) {\n  if (err instanceof UserError && err.message === 'Cannot update system roles') {\n    // tell the user to create a custom role instead\n  } else throw err;\n}","preventionTips":["Filter system roles out of bulk update operations.","Prefer creating custom roles over modifying built-in ones.","In the UI, hide/disable edit controls for system roles."],"tags":["rbac","role","system","immutable"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}