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

  1. Verify the slug exists: `await roleRepository.findOneBy({ slug })` before calling update.
  2. Check slug casing and trailing whitespace — slugs are typically case-sensitive.
  3. If the role was deleted intentionally, update callers to stop referencing it.
  4. 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

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


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