{"record":{"id":"e471364452a289aa","repo":"n8n-io/n8n","slug":"role-not-found","errorCode":null,"errorMessage":"Role not found","messagePattern":"Role not found","errorType":"exception","errorClass":"UserError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/db/src/repositories/role.repository.ts","lineNumber":238,"sourceCode":"\n\t\t\tconst result = await trx.delete(Role, { slug: role.slug });\n\t\t\tif (result.affected !== 1) {\n\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}","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/db/src/repositories/role.repository.ts#L220-L256","documentation":"UserError thrown by RoleRepository.updateEntityWithManager when no Role row matches the provided slug (findOne with relations: ['scopes'] returns null). Indicates the caller asked to update a role that does not exist. Followed immediately by a separate guard for system roles.","triggerScenarios":"Calling roleRepository.update(slug, ...) with a slug that was deleted, never existed, or is misspelled. Common in RBAC admin flows, programmatic role editing, or tests using fixture slugs that haven't been seeded.","commonSituations":"API/CLI request to update a custom role by an old/renamed slug; a race where the role was deleted between read and update; seed scripts assuming a role exists without seeding it first; case mismatch on slug.","solutions":["Verify the slug exists: `await roleRepository.findOneBy({ slug })` before calling update.","Check slug casing and trailing whitespace — slugs are typically case-sensitive.","If the role was deleted intentionally, update callers to stop referencing it.","For seed-dependent flows, ensure role seeding runs before any update path."],"exampleFix":"// before\nawait roleRepository.update('old-slug', { displayName: 'X' });\n\n// after\nconst existing = await roleRepository.findOneBy({ slug: 'old-slug' });\nif (!existing) throw new UserError(`Cannot update: role 'old-slug' does not exist`);\nawait roleRepository.update('old-slug', { displayName: 'X' });","handlingStrategy":"validation","validationCode":"const existing = await roleRepository.findOneBy({ slug });\nif (!existing) {\n  // do not call update; surface 'role not found' to caller\n}","typeGuard":"async function roleExists(slug: string): Promise<boolean> {\n  return await roleRepository.existsBy({ slug });\n}","tryCatchPattern":"try {\n  await roleRepository.update(slug, patch);\n} catch (err) {\n  if (err instanceof UserError && err.message === 'Role not found') {\n    // 404 to the caller\n  } else throw err;\n}","preventionTips":["Check role existence before update in admin flows.","Slugs are case-sensitive — normalize and validate casing.","Seed required roles before any code path that updates them."],"tags":["rbac","role","not-found","repository"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}