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);