toeverything/AFFiNE · error · NewOwnerIsNotActiveMember
new_owner_is_not_active_member
new_owner_is_not_active_member
Error message
Can not set a non-active member as owner.
What it means
WorkspaceUser ownership transfer delegates to workspaceMember.setOwner(); when that model rejects the target with the plain Error 'New workspace owner must be an active member.', this wrapper translates it to NewOwnerIsNotActiveMember. The designated owner must hold an ACTIVE membership in the workspace — pending invitees, revoked, or departed members do not qualify.
Solutions
- Refresh the member list and choose a member whose membership status is active before confirming the transfer.
- If the intended owner departed or was revoked, re-invite them and wait for acceptance, then transfer.
- Check the target's membership record (status/role) before calling the transfer API.
Example fix
// before
await workspaceUser.transferOwner(workspaceId, pendingInviteeId, 'admin'); // invite not accepted -> NewOwnerIsNotActiveMember
// after
const member = await getMember(workspaceId, newOwnerId);
if (!member || member.status !== 'active') {
throw new Error('New owner must be an active member.');
}
await workspaceUser.transferOwner(workspaceId, newOwnerId, 'admin'); Defensive patterns
Strategy: validation
Validate before calling
const membership = await models.workspaceMember.get(workspaceId, newOwnerId);
if (!membership || membership.status !== 'active') {
throw new Error('New owner must be an active member.');
}
await workspaceUser.transferOwner(workspaceId, newOwnerId, fallbackRole); Type guard
function isActiveMember(m: { status?: string } | null | undefined): m is { status: 'active' } {
return !!m && m.status === 'active';
} Try / catch
try {
await workspaceUser.transferOwner(workspaceId, newOwnerId, 'admin');
} catch (e) {
if (e instanceof NewOwnerIsNotActiveMember) {
// re-invite the user or pick another member
} else throw e;
} Prevention
- Filter transfer-target pickers to active memberships only.
- Re-fetch the member list right before confirming a transfer.
- Remember the underlying setOwner requires active membership — pending invitees never qualify.
When it happens
Trigger: Transferring workspace ownership to a userId whose membership in that workspace is not active — invitation not yet accepted, membership revoked, or member has left.
Common situations: UI member picker lists pending invitees as transfer targets; the chosen owner left the workspace between selection and confirmation; an admin revoked the membership concurrently.
Related errors
- cannot_delete_account_with_owned_team_workspace
- already_in_space
- authentication_required
- copilot_session_not_found
- copilot_session_not_found
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/a6d05f0e17672c26.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/models/workspace-user.ts:91
},
});
const fallbackRole =
oldOwner && (await this.models.workspace.isTeamWorkspace(workspaceId))
? WorkspaceRole.Admin
: WorkspaceRole.Collaborator;
try {
await this.models.workspaceMember.setOwner(
workspaceId,
userId,
fallbackRole
);
} catch (error) {
if (
error instanceof Error &&
error.message === 'New workspace owner must be an active member.'
) {
throw new NewOwnerIsNotActiveMember();
}
throw error;
}
if (oldOwner?.user && oldOwner.user.id !== userId) {
this.event.emit('workspace.owner.changed', {
workspaceId,
from: oldOwner.user.id,
to: userId,
});
this.logger.log(
`Transfer workspace owner of [${workspaceId}] from [${oldOwner.user.id}] to [${userId}]`
);
} else {
this.logger.log(`Set workspace owner of [${workspaceId}] to [${userId}]`);
}
}
View on GitHub (pinned to 2af30773ae)