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
Thrown by WorkspaceUserModel.setOwner (workspace-user.ts:93), which wraps WorkspaceMemberModel.setOwner. The inner method refuses to install a new owner when the workspace already has an owner AND the target user has no active workspace_members row (findFirst with state:'active' returns null). The wrapper catches that inner plain Error ('New workspace owner must be an active member.') and rethrows it as NewOwnerIsNotActiveMember. UserFriendlyError, code new_owner_is_not_active_member, type bad_request, HTTP 400.
Source
Thrown at packages/backend/server/src/models/workspace-user.ts:93
});
const fallbackRole = (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 26c515e050)
Solutions
- Invite the target user and have them accept first, so an active workspace_members row exists, then transfer.
- If the user was already invited, transition them to Accepted (setState) to activate the membership, then call setOwner.
- Choose a user who is already an active member as the new owner.
Example fix
// before await models.workspaceUser.setOwner(wsId, pendingUserId); // after await models.workspaceInvitation.setState(wsId, pendingUserId, WorkspaceMemberStatus.Accepted); await models.workspaceUser.setOwner(wsId, pendingUserId);
Defensive patterns
Strategy: validation
Validate before calling
async function transferOwnerSafe(models, wsId: string, newOwnerId: string) {
const active = await models.db.workspaceMember.findFirst({
where: { workspaceId: wsId, userId: newOwnerId, state: 'active' },
});
if (!active) {
throw new Error('Target user is not an active member; invite and accept first.');
}
return models.workspaceUser.setOwner(wsId, newOwnerId);
} Try / catch
import { NewOwnerIsNotActiveMember } from '../base/error/errors.gen';
try {
await models.workspaceUser.setOwner(wsId, newOwnerId);
} catch (e) {
if (e instanceof NewOwnerIsNotActiveMember) {
// invite/accept the user first, then retry
} else throw e;
} Prevention
- Ensure the target user is an active member before transferring ownership.
- Restrict the 'pick new owner' UI to currently active members only.
- Resolve pending invitations to Accepted before calling setOwner.
When it happens
Trigger: Calling transfer-ownership (setOwner) for a user who has not joined the workspace or whose invitation is still pending/revoked, when the workspace already has an active owner.
Common situations: Admin selects a pending invitee (not yet active) as the new owner; UI lets you pick any known user instead of only active members; a race between member removal and the ownership transfer.
Related errors
- Cannot remove the last active workspace owner.
- space_access_denied
- doc_update_blocked
- expect_to_revoke_doc_user_roles
- expect_to_update_doc_user_role
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/a6d05f0e17672c26.
Report an issue: GitHub.