n8n-io/n8n · error · ForbiddenError

Owner cannot change role on global owner

Error message

Owner cannot change role on global owner

What it means

Returned by PATCH /users/:id/role when both requester and target hold the global owner role (GLOBAL_OWNER_ROLE.slug). Even the owner cannot reassign another owner's role through this endpoint. Message is the NO_OWNER_ON_OWNER constant. HTTP 403.

Source

Thrown at packages/cli/src/controllers/users.controller.ts:379

			where: { id },
			relations: ['role'],
		});
		if (targetUser === null) {
			throw new NotFoundError(NO_USER);
		}

		if (
			req.user.role.slug === GLOBAL_ADMIN_ROLE.slug &&
			targetUser.role.slug === GLOBAL_OWNER_ROLE.slug
		) {
			throw new ForbiddenError(NO_ADMIN_ON_OWNER);
		}

		if (
			req.user.role.slug === GLOBAL_OWNER_ROLE.slug &&
			targetUser.role.slug === GLOBAL_OWNER_ROLE.slug
		) {
			throw new ForbiddenError(NO_OWNER_ON_OWNER);
		}

		await this.userService.changeUserRole(targetUser, payload);

		this.eventService.emit('user-changed-role', {
			userId: req.user.id,
			targetUserId: targetUser.id,
			targetUserNewRole: payload.newRoleName,
			publicApi: false,
		});

		return { success: true };
	}
}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Reassign ownership through the documented ownership-transfer flow rather than this endpoint.
  2. Filter peer owners out of role-change targets.
  3. Document that owner role transitions are not supported via PATCH /users/:id/role.
Defensive patterns

Strategy: validation

Validate before calling

function canChangeRole(requesterSlug: string, targetSlug: string) {
  return !(requesterSlug === 'global:owner' && targetSlug === 'global:owner');
}
if (!canChangeRole(reqUser.role.slug, target.role.slug)) {
  throw new Error('Owner-to-owner role changes are blocked');
}

Type guard

const isOwner = (s: string) => s === 'global:owner';

Try / catch

try { await fetch(`/rest/users/${id}/role`, { method: 'PATCH', body }); }
catch (e) { if (e.statusCode === 403 && /change role on global owner/.test(e.message)) { /* use transfer flow */ } else throw e; }

Prevention

When it happens

Trigger: A global owner calls PATCH /users/<another-owner-id>/role; both slug comparisons match GLOBAL_OWNER_ROLE.slug.

Common situations: Multi-owner setup; scripted role management that includes peer owners; misunderstanding that owner-to-owner role changes are blocked even for the owner.

Related errors


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