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

  1. Refresh the member list and choose a member whose membership status is active before confirming the transfer.
  2. If the intended owner departed or was revoked, re-invite them and wait for acceptance, then transfer.
  3. 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

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


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)