toeverything/AFFiNE · error · Error
Cannot remove the last active workspace owner.
Error message
Cannot remove the last active workspace owner.
What it means
Thrown by WorkspaceMemberModel.delete (permission-write.ts:254) when removing a user would leave the workspace with zero active owners. The method first takes a pg_advisory_xact lock and a SELECT ... FOR UPDATE on owner rows, then counts other active owners; if the target is an owner and no other active owner exists, it refuses the delete so a workspace is never orphaned. Note this is a plain Error (not a UserFriendlyError), so unless a higher layer maps it, it surfaces as an internal_server_error.
Source
Thrown at packages/backend/server/src/models/permission-write.ts:254
const existingOwners = await this.db.workspaceMember.count({
where: {
workspaceId,
role: 'owner',
state: 'active',
userId: { not: userId },
},
});
const deletingOwner = await this.db.workspaceMember.count({
where: {
workspaceId,
userId,
role: 'owner',
state: 'active',
},
});
if (deletingOwner > 0 && existingOwners === 0) {
throw new Error('Cannot remove the last active workspace owner.');
}
return await this.db.workspaceMember.deleteMany({
where: { workspaceId, userId, state: 'active' },
});
}
}
@Injectable()
export class WorkspaceInvitationModel extends BaseModel {
@Transactional()
async set(
workspaceId: string,
userId: string,
role: WorkspaceRole,
status: WorkspaceMemberStatus,
data: {
source?: WorkspaceMemberSource;View on GitHub (pinned to 26c515e050)
Solutions
- Promote another active member to owner first via WorkspaceUserModel.setOwner(workspaceId, otherMemberId) before calling delete.
- If the intent is to leave, transfer ownership to a different active member, then retry the removal.
- If the workspace should be destroyed, delete the workspace itself rather than removing its owner.
Example fix
// before await models.workspaceMember.delete(wsId, ownerUserId); // after await models.workspaceUser.setOwner(wsId, otherMemberId); await models.workspaceMember.delete(wsId, ownerUserId);
Defensive patterns
Strategy: validation
Validate before calling
async function safeRemoveOwner(models, wsId: string, userId: string) {
const otherOwners = await models.db.workspaceMember.count({
where: { workspaceId: wsId, role: 'owner', state: 'active', userId: { not: userId } },
});
if (otherOwners === 0) {
throw new Error('Refusing to remove the last active workspace owner; transfer ownership first.');
}
return models.workspaceMember.delete(wsId, userId);
} Prevention
- Before removing an owner, ensure at least one other active owner remains.
- Route 'leave workspace' for the current owner through setOwner transfer first.
- Disable the UI 'Leave workspace' control when the current user is the only active owner.
When it happens
Trigger: Calling the workspace member removal / 'leave workspace' / revoke-permission path for the only active owner of a workspace. Reproduce: create a workspace (you become the sole owner), invite no second owner, then call WorkspaceMemberModel.delete(workspaceId, ownerUserId).
Common situations: The sole workspace owner clicks 'Leave workspace'; an automated offboarding script removes members and accidentally targets the owner; a prior ownership transfer failed silently so the delete now strands the workspace.
Related errors
- Cannot remove the last doc owner grant.
- new_owner_is_not_active_member
- space_access_denied
- doc_update_blocked
- expect_to_revoke_doc_user_roles
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/9e9e56f1acbf44c2.
Report an issue: GitHub.