diff --git a/src/main/java/it/inaf/oats/vospace/CopyService.java b/src/main/java/it/inaf/oats/vospace/CopyService.java
index bba6b6be692c263338cf2dc85c1d67a0fea14ad6..28c03d873aadf22cd17fcab6386755236ceccdd9 100644
--- a/src/main/java/it/inaf/oats/vospace/CopyService.java
+++ b/src/main/java/it/inaf/oats/vospace/CopyService.java
@@ -28,7 +28,7 @@ public class CopyService extends AbstractNodeService {
     private NodeBranchService nodeBranchService;
 
     @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ)
-    public List<String> processCopyNodes(Transfer transfer, String jobId, User user) {
+    public String processCopyNodes(Transfer transfer, String jobId, User user) {
         if (transfer.getTarget().size() != 1) {
             throw new InvalidArgumentException("Invalid target size for copyNode: " + transfer.getTarget().size());
         }
@@ -39,14 +39,16 @@ public class CopyService extends AbstractNodeService {
         // Get Destination Vos Path (it's in transfer direction)
         String destinationPath = URIUtils.returnVosPathFromNodeURI(transfer.getDirection(), authority);
 
+        // Destination source to be returned, null if no copy was performed
+        String destinationCopyRoot = null;
+
         this.validatePath(sourcePath);
         this.validatePath(destinationPath);
 
         if (sourcePath.equals(destinationPath)) {
-            return List.of();
+            return null;
         }
 
-        List<String> copiedNodesPaths = null;
         try {
 
             // check source branch for read and lock it
@@ -58,10 +60,7 @@ public class CopyService extends AbstractNodeService {
 
             if (destShortNodeDescriptor.isPresent()) {
                 this.validateDestinationContainer(destShortNodeDescriptor.get(), destinationPath);
-                copiedNodesPaths = nodeDao.copyBranch(
-                        sourcePath,
-                        destinationPath + "/" + NodeUtils.getNodeName(sourcePath),
-                        jobId);
+                destinationCopyRoot = destinationPath + "/" + NodeUtils.getNodeName(sourcePath);
 
             } else {
                 // Check if parent exists
@@ -70,8 +69,7 @@ public class CopyService extends AbstractNodeService {
                         = nodeDao.getShortNodeDescriptor(destinationParentPath, user.getName(), user.getGroups());
                 if (destShortNodeDescriptorParent.isPresent()) {
                     this.validateDestinationContainer(destShortNodeDescriptorParent.get(), destinationParentPath);
-                    copiedNodesPaths = nodeDao.copyBranch(sourcePath,
-                            destinationPath, jobId);
+                    destinationCopyRoot = destinationPath;
 
                 } else {
                     throw new UnsupportedOperationException("Creation of destination upon copy not supported");
@@ -79,12 +77,17 @@ public class CopyService extends AbstractNodeService {
 
             }
 
+            nodeDao.copyBranch(
+                    sourcePath,
+                    destinationCopyRoot,
+                    jobId);
+
         } catch (CannotSerializeTransactionException ex) {
             // Concurrent transactions attempted to modify this set of nodes            
             throw new NodeBusyException(sourcePath);
         }
 
-        return copiedNodesPaths;
+        return destinationCopyRoot;
 
     }
 
diff --git a/src/main/java/it/inaf/oats/vospace/JobService.java b/src/main/java/it/inaf/oats/vospace/JobService.java
index ca99fcad4b2ec3a03f354c4ec6cb8da3bc217bb2..e74b84fafe775fef27778a839d63a4e034041772 100644
--- a/src/main/java/it/inaf/oats/vospace/JobService.java
+++ b/src/main/java/it/inaf/oats/vospace/JobService.java
@@ -39,6 +39,9 @@ public class JobService {
 
     @Autowired
     private CopyService copyService;
+    
+    @Autowired
+    private NodeBranchService nodeBranchService;
 
     @Autowired
     private AsyncTransferService asyncTransfService;
@@ -179,8 +182,9 @@ public class JobService {
             handleJobErrors(jobSummary, job -> {
                 copyService.processCopyNodes(transfer, jobSummary.getJobId(), user);
                 // add file service copy logic
-
-                job.setPhase(ExecutionPhase.COMPLETED);
+                
+                // the file service part will unlock nodes and set job phase
+                // to completed
             });
         });
     }
diff --git a/src/main/java/it/inaf/oats/vospace/NodeBranchService.java b/src/main/java/it/inaf/oats/vospace/NodeBranchService.java
index 2eb3b872653fa2aa6229de37876239f9ebc34190..61d64ff449b28d262f8ff16072d3584c47d71c8d 100644
--- a/src/main/java/it/inaf/oats/vospace/NodeBranchService.java
+++ b/src/main/java/it/inaf/oats/vospace/NodeBranchService.java
@@ -62,6 +62,10 @@ public class NodeBranchService {
 
         nodeDao.setBranchJobId(sourceRootId, jobId);
 
-    }   
+    }
+    
+    public void unlockNodes(String jobId) {
+        nodeDao.releaseBusyNodesByJobId(jobId);
+    }
 
 }
diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
index c412581c9c91bdd73b76adc54970693812fff9ca..802e5c8c8daf3bf5a4f676b4bebf06921a1a200c 100644
--- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
+++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
@@ -344,7 +344,7 @@ public class NodeDAO {
         });
     }
 
-    public List<String> copyBranch(String sourceVosPath, String destVosPath, String jobId) {
+    public void copyBranch(String sourceVosPath, String destVosPath, String jobId) {
         
         String destVosParentPath = NodeUtils.getParentPath(destVosPath);
         String destName = NodeUtils.getNodeName(destVosPath);
@@ -375,8 +375,6 @@ public class NodeDAO {
                 + "CASE WHEN nlevel(new_parent_path) = rel_offset THEN ''::ltree ELSE subpath(new_parent_path, rel_offset) END new_parent_relative_path,\n"
                 + "name, type, location_id, creator_id, group_write, group_read, is_public, ?\n"
                 + "FROM copied_nodes_paths\n";
-        
-        String returning = "RETURNING get_vos_path(node_id) AS returning_path";
 
         String sql = parentInsert
                 + "WITH RECURSIVE path_prefix AS ("
@@ -385,19 +383,15 @@ public class NodeDAO {
                 + cteCopiedNodes + "),\n"
                 + "copied_nodes_paths AS ("
                 + cteCopiedNodesPaths + ")\n"
-                + parentSelect
-                + returning;
-                
-        
-        return jdbcTemplate.query(conn -> {
+                + parentSelect;
+             
+        jdbcTemplate.update(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
             ps.setString(1, destVosParentPath);
             ps.setString(2, destName);
             ps.setString(3, sourceVosPath);
             ps.setString(4, jobId);
             return ps;
-        }, (rs, row) -> {
-            return rs.getString("returning_path");
         });      
         
     }
@@ -423,6 +417,16 @@ public class NodeDAO {
             return ps;
         });
     }
+    
+    public void releaseBusyNodesByJobId(String jobId) {
+        String sql = "UPDATE node SET job_id = NULL WHERE job_id = ?";
+        
+        jdbcTemplate.update(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, jobId);           
+            return ps;
+        });        
+    }
 
     public boolean isBranchWritable(long parentNodeId, String userId, List<String> userGroups) {
 
diff --git a/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java b/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java
index e1927ab0a44a4cbc8210ba580dc71c94df8677c3..3bd1aa8fda4be077c4bf22fe963ae2cdc67a336a 100644
--- a/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java
+++ b/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java
@@ -15,6 +15,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import net.ivoa.xml.vospace.v2.Transfer;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
@@ -151,7 +152,10 @@ public class CopyServiceTest {
         assertTrue(destId.isPresent());
 
         // copy
-        copyService.processCopyNodes(getTransfer("/test3/m1", "/test4"), "job_pippo", user);
+        String copyDestination
+                = copyService.processCopyNodes(getTransfer("/test3/m1", "/test4"), "job_pippo", user);
+
+        assertEquals("/test4/m1", copyDestination);
 
         // source has been moved
         Optional<Long> oldSourceId = nodeDao.getNodeId("/test3/m1");
@@ -183,7 +187,10 @@ public class CopyServiceTest {
         assertTrue(destId.isEmpty());
 
         // copy
-        copyService.processCopyNodes(getTransfer("/test3/m1/m2", "/test3/m1/m2_copy"), "job_pippo", user);
+        String copyDestination
+                = copyService.processCopyNodes(getTransfer("/test3/m1/m2", "/test3/m1/m2_copy"), "job_pippo", user);
+
+        assertEquals("/test3/m1/m2_copy", copyDestination);
 
         // source has been moved
         Optional<Long> oldSourceId = nodeDao.getNodeId("/test3/m1");
@@ -202,7 +209,7 @@ public class CopyServiceTest {
 
         User user = mock(User.class);
         when(user.getName()).thenReturn("user3");
-        
+
         // Preliminary checks for assumptions
         Optional<Long> sourceId = nodeDao.getNodeId("/test3/m1");
         assertTrue(sourceId.isPresent());
diff --git a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
index 132d0b737298165f2539153474c18dd6a804645c..dd03781ce8d5f39dd06b5047d9830d598cafa278 100644
--- a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
+++ b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
@@ -48,7 +48,8 @@ public class NodeDAOTest {
         dao = new NodeDAO(dataSource);
         ReflectionTestUtils.setField(dao, "authority", AUTHORITY);
     }
-
+    
+    
     @Test
     public void testCreateNode() {
         DataNode dataNode = new DataNode();
@@ -282,6 +283,7 @@ public class NodeDAOTest {
 
     }
 
+
     @Test
     public void testCopyNodeBranch() {
         // Let's copy /test3/m1 to /test3/group1
@@ -294,13 +296,7 @@ public class NodeDAOTest {
         Optional<Long> optDestParentId = dao.getNodeId("/test3/group1");
         assertTrue(optDestParentId.isPresent());
 
-        List<String> returnedPaths = 
-                dao.copyBranch("/test3/m1", "/test3/group1/copy_of_m1", "pippo");
-        
-        assertEquals(2, returnedPaths.size());
-        assertTrue(returnedPaths.containsAll(
-                        List.of("/test3/group1/copy_of_m1", 
-                                "/test3/group1/copy_of_m1/m2")));
+        dao.copyBranch("/test3/m1", "/test3/group1/copy_of_m1", "pippo");
         
         Optional<Long> resultId = dao.getNodeId("/test3/group1/copy_of_m1");
         assertTrue(resultId.isPresent());
@@ -469,7 +465,30 @@ public class NodeDAOTest {
     public void testGetNodeOsName() {
         assertEquals("f2", dao.getNodeOsName("/test1/f1/f2_renamed"));
         assertEquals("f4", dao.getNodeOsName("/test2/f4"));
-    }
+    }   
+    
+    @Test
+    public void testReleaseNodesByJobId(){
+        Optional<Long> optId = dao.getNodeId("/test3/m1");
+        assertTrue(optId.isPresent());
+        
+        assertFalse(dao.isBranchBusy(optId.get()));
+        
+        dao.setBranchJobId(optId.get(), "pippo1");
+        
+        assertTrue(dao.isBranchBusy(optId.get()));
+        
+        Optional<Long> childId = dao.getNodeId("/test3/m1/m2");
+        assertTrue(childId.isPresent());
+        
+        assertTrue(dao.isBranchBusy(childId.get()));
+        
+        dao.releaseBusyNodesByJobId("pippo1");
+        
+        assertFalse(dao.isBranchBusy(optId.get()));
+        assertFalse(dao.isBranchBusy((childId.get())));    
+               
+    }       
 
     private Property getProperty(String uri, String value) {
         Property property = new Property();