n8n-io/n8n · error · UserError
Cannot update system roles
Error message
Cannot update system roles
What it means
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.
Source
Thrown at packages/@n8n/db/src/repositories/role.repository.ts:241
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;
}
return await entityManager.save<Role>(role);
}View on GitHub (pinned to 5ac6606e81)
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.
Example fix
// before
for (const slug of allSlugs) await roleRepo.update(slug, patch);
// after
for (const role of await roleRepo.find()) {
if (role.systemRole) continue;
await roleRepo.update(role.slug, patch);
} Defensive patterns
Strategy: type-guard
Validate before calling
const role = await roleRepository.findOneBy({ slug });
if (!role) { /* not-found path */ }
if (role.systemRole) { /* refuse to update; create a custom role instead */ } Type guard
function isEditableRole(role: Role): boolean {
return !role.systemRole;
} Try / catch
try {
await roleRepository.update(slug, patch);
} catch (err) {
if (err instanceof UserError && err.message === 'Cannot update system roles') {
// tell the user to create a custom role instead
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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'.
Related errors
- Role not found
- Down migration only possible when there are no projects. Ple
- User is missing a scope required to perform this action
- error.message
- Cannot invite admin user without advanced permissions. Pleas
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/7e933b0f96de4fab.
Report an issue: GitHub.