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

  1. Do not modify system roles — if you need different scopes, create a custom role and assign users to it.
  2. Filter system roles out before bulk operations: `if (!role.systemRole) await update(...)`.
  3. Surface a clear UI message: 'Built-in roles cannot be edited; create a custom role instead.'
  4. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/7e933b0f96de4fab. Report an issue: GitHub.