n8n-io/n8n · error · UserError
Role not found
Error message
Role not found
What it means
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.
Source
Thrown at packages/@n8n/db/src/repositories/role.repository.ts:238
const result = await trx.delete(Role, { slug: role.slug });
if (result.affected !== 1) {
throw new Error(`Failed to delete role "${role.slug}"`);
}
});
}
private async updateEntityWithManager(
entityManager: EntityManager,
slug: string,
newData: Partial<Pick<Role, 'description' | 'scopes' | 'displayName'>>,
) {
const role = await entityManager.findOne(Role, {
where: { slug },
relations: ['scopes'],
});
if (!role) {
throw new UserError('Role not found');
}
if (role.systemRole) {
throw new UserError('Cannot update system roles');
}
// Only update fields that are explicitly provided (not undefined)
// This preserves existing scopes when scopes is undefined
if (newData.displayName !== undefined) {
role.displayName = newData.displayName;
}
if (newData.description !== undefined) {
role.description = newData.description;
}
if (newData.scopes !== undefined) {
role.scopes = newData.scopes;
}View on GitHub (pinned to 5ac6606e81)
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.
Example fix
// before
await roleRepository.update('old-slug', { displayName: 'X' });
// after
const existing = await roleRepository.findOneBy({ slug: 'old-slug' });
if (!existing) throw new UserError(`Cannot update: role 'old-slug' does not exist`);
await roleRepository.update('old-slug', { displayName: 'X' }); Defensive patterns
Strategy: validation
Validate before calling
const existing = await roleRepository.findOneBy({ slug });
if (!existing) {
// do not call update; surface 'role not found' to caller
} Type guard
async function roleExists(slug: string): Promise<boolean> {
return await roleRepository.existsBy({ slug });
} Try / catch
try {
await roleRepository.update(slug, patch);
} catch (err) {
if (err instanceof UserError && err.message === 'Role not found') {
// 404 to the caller
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot update system roles
- Workflow "${workflowId}" not found.
- error.message
- Target user not found
- Workflow ${workflowId} does not exist.
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/e471364452a289aa.
Report an issue: GitHub.