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

  1. Invite the target user and have them accept first, so an active workspace_members row exists, then transfer.
  2. If the user was already invited, transition them to Accepted (setState) to activate the membership, then call setOwner.
  3. 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

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


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/a6d05f0e17672c26. Report an issue: GitHub.