From 41789177d3f3d97b4924f36b57c7d8cf05f5cdd2 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 18 Sep 2025 19:30:38 +0000 Subject: [PATCH] fix: prevent conversation history loss when canceling during response - Add explicit saveApiConversationHistory() call after adding interrupted message in abortStream - Reverse order of abortStream/abortTask calls to save state before cleanup - Add safety check in saveApiMessages to prevent overwriting non-empty history with empty array Fixes #8153 --- src/core/task-persistence/apiMessages.ts | 26 ++++++++++++++++++++++++ src/core/task/Task.ts | 8 ++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/core/task-persistence/apiMessages.ts b/src/core/task-persistence/apiMessages.ts index f846aaf13f..96c0e8442a 100644 --- a/src/core/task-persistence/apiMessages.ts +++ b/src/core/task-persistence/apiMessages.ts @@ -79,5 +79,31 @@ export async function saveApiMessages({ }) { const taskDir = await getTaskDirectoryPath(globalStoragePath, taskId) const filePath = path.join(taskDir, GlobalFileNames.apiConversationHistory) + + // Safety check: prevent saving an empty array if a non-empty conversation previously existed + if (Array.isArray(messages) && messages.length === 0) { + // Check if there's an existing non-empty conversation history + if (await fileExistsAtPath(filePath)) { + try { + const existingContent = await fs.readFile(filePath, "utf8") + const existingData = JSON.parse(existingContent) + if (Array.isArray(existingData) && existingData.length > 0) { + console.error( + `[Roo-Debug] saveApiMessages: Attempted to save empty array over existing non-empty conversation. ` + + `TaskId: ${taskId}, Existing messages count: ${existingData.length}. ` + + `Skipping save to prevent data loss.`, + ) + return // Don't save empty array over non-empty conversation + } + } catch (error) { + // If we can't read/parse the existing file, proceed with save + console.error( + `[Roo-Debug] saveApiMessages: Error checking existing conversation history. ` + + `TaskId: ${taskId}, Error: ${error}. Proceeding with save.`, + ) + } + } + } + await safeWriteJson(filePath, messages) } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index cf16df8dcc..5520c2fc40 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1904,6 +1904,9 @@ export class Task extends EventEmitter implements TaskLike { ], }) + // Explicitly save the API conversation history after adding the interrupted message + await this.saveApiConversationHistory() + // Update `api_req_started` to have cancelled and cost, so that // we can display the cost of the partial stream. updateApiReqMsg(cancelReason, streamingFailedMessage) @@ -2196,9 +2199,10 @@ export class Task extends EventEmitter implements TaskLike { ? undefined : (error.message ?? JSON.stringify(serializeError(error), null, 2)) - // Now call abortTask after determining the cancel reason. - await this.abortTask() + // Reverse the order: call abortStream first to properly save the interrupted message + // before abortTask cleans up resources await abortStream(cancelReason, streamingFailedMessage) + await this.abortTask() const history = await provider?.getTaskWithId(this.taskId)