This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new e9f9e617d fix(ai): remove the agent workspace with every purged 
conversation (#4754)
e9f9e617d is described below

commit e9f9e617d24d6b77c4b268f3bcc0e07b1399dd1a
Author: 烤化の初雪 <[email protected]>
AuthorDate: Thu Sep 24 18:03:00 2026 +0800

    fix(ai): remove the agent workspace with every purged conversation (#4754)
    
    fix(ai): remove the agent workspace with every purged conversation
    
    The retention pass deleted events, runs and the conversation row but
    never the conversation's workspace, while the on-request delete does:
    the workspace holds the child's HOME, so the agent transcript
    (home/.claude/projects/<cwd>/<session>.jsonl) and the rmqctl config
    stayed on disk forever under an id nothing refers to any more. The
    default retention is 90 days on a persistent directory, so the
    transcripts of "expired" conversations accumulated without bound.
    
    Delete the workspace too, after the rows, the order delete() uses: a
    crash in between leaves a directory that is still reachable, whereas
    removing the transcript of a conversation that is still visible is not
    recoverable.
---
 .../ops/ai/conversation/AiConversationService.java     | 17 ++++++++++++++---
 .../ops/ai/conversation/AiConversationServiceTest.java | 18 ++++++++++++++++++
 2 files changed, 32 insertions(+), 3 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationService.java
index 93d494b15..234d11e3f 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationService.java
@@ -78,7 +78,8 @@ import java.util.Set;
  * <h2>Retention cascades by hand</h2>
  * This project declares no foreign keys, so deleting a conversation has to 
delete its runs and events
  * itself, and the order is events, runs, conversations: a crash halfway 
leaves a parent with no
- * children, which the next pass finishes, instead of children nobody can find 
any more.
+ * children, which the next pass finishes, instead of children nobody can find 
any more. The agent
+ * workspace goes with the rows, because the conversation's lifetime is what 
keeps it on disk.
  */
 @Slf4j
 @Service
@@ -496,11 +497,21 @@ public class AiConversationService implements 
ApplicationRunner {
         return totalDeleted;
     }
 
-    /** Events, then runs, then conversations: no FK constraints here, so the 
cascade is this method. */
+    /**
+     * Events, then runs, then conversations: no FK constraints here, so the 
cascade is this method.
+     *
+     * <p>The workspace goes too, once the rows are gone: it holds the child's 
{@code HOME}, so the
+     * agent transcript lives there and a purge that only deletes rows keeps 
it on disk under an id
+     * nothing refers to any more. After the rows, not before - the workspace 
of a conversation a
+     * crash left visible is still reachable, while a transcript removed under 
a live conversation
+     * is not (the order {@link #delete(Long, String)} uses).
+     */
     private int deleteCascade(List<Long> conversationIds) {
         eventRepository.deleteByConversationIds(conversationIds);
         runRepository.deleteByConversationIds(conversationIds);
-        return conversationRepository.deleteByIds(conversationIds);
+        int deleted = conversationRepository.deleteByIds(conversationIds);
+        conversationIds.forEach(workspace::delete);
+        return deleted;
     }
 
     /**
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationServiceTest.java
index d4ac7c5ec..727d8651d 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/conversation/AiConversationServiceTest.java
@@ -368,6 +368,24 @@ class AiConversationServiceTest {
         verify(conversationRepository, times(1)).findIdsCreatedBefore(any(), 
anyInt());
     }
 
+    @Test
+    void retentionShouldRemoveTheWorkspaceOfEveryPurgedConversationTest() {
+        
when(conversationRepository.findIdsCreatedBefore(any(LocalDateTime.class), 
eq(500)))
+                .thenReturn(List.of(1L, 2L, 3L));
+        when(conversationRepository.deleteByIds(anyList())).thenReturn(3);
+
+        service.purgeExpired();
+
+        // The workspace holds the child's HOME, so the agent transcript lives 
there: a purge that
+        // only deletes rows keeps it on disk forever, under a conversation 
the user cannot see any
+        // more. It is removed after the rows, the same order the on-request 
delete uses.
+        InOrder order = inOrder(conversationRepository, workspace);
+        order.verify(conversationRepository).deleteByIds(List.of(1L, 2L, 3L));
+        order.verify(workspace).delete(1L);
+        order.verify(workspace).delete(2L);
+        order.verify(workspace).delete(3L);
+    }
+
     @Test
     void 
retentionShouldKeepBatchingUntilTheBacklogIsGoneOrTheCapIsReachedTest() {
         properties.setCleanupBatchSize(2);

Reply via email to