From 49a68c1abd00c576ccc5bbcaa4ccce2fdf1f61ec Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Tue, 3 Jun 2025 21:55:34 -0700 Subject: [PATCH 1/2] fix: add debug logging for API conversation history issues Add detailed logging to readApiMessages function to help diagnose "Unexpected: No existing API conversation history" errors. The logs will show: - When the history file exists but contains an empty array - When the old history file exists but contains an empty array - When neither history file is found This will help diagnose issues like the one in #4311 where the API conversation history file gets truncated to an empty array during task resumption with orchestrator tasks. Fixes: #4311 Signed-off-by: Eric Wheeler --- src/core/task-persistence/apiMessages.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/core/task-persistence/apiMessages.ts b/src/core/task-persistence/apiMessages.ts index 0ba9628a5d8..01d9889d312 100644 --- a/src/core/task-persistence/apiMessages.ts +++ b/src/core/task-persistence/apiMessages.ts @@ -21,17 +21,34 @@ export async function readApiMessages({ const filePath = path.join(taskDir, GlobalFileNames.apiConversationHistory) if (await fileExistsAtPath(filePath)) { - return JSON.parse(await fs.readFile(filePath, "utf8")) + const fileContent = await fs.readFile(filePath, "utf8") + const parsedData = JSON.parse(fileContent) + if (Array.isArray(parsedData) && parsedData.length === 0) { + console.error( + `[Roo-Debug] readApiMessages: Found API conversation history file, but it's empty. TaskId: ${taskId}, Path: ${filePath}`, + ) + } + return parsedData } else { const oldPath = path.join(taskDir, "claude_messages.json") if (await fileExistsAtPath(oldPath)) { - const data = JSON.parse(await fs.readFile(oldPath, "utf8")) + const fileContent = await fs.readFile(oldPath, "utf8") + const parsedData = JSON.parse(fileContent) + if (Array.isArray(parsedData) && parsedData.length === 0) { + console.error( + `[Roo-Debug] readApiMessages: Found OLD API conversation history file (claude_messages.json), but it's empty. TaskId: ${taskId}, Path: ${oldPath}`, + ) + } await fs.unlink(oldPath) - return data + return parsedData } } + // If we reach here, neither the new nor the old history file was found. + console.error( + `[Roo-Debug] readApiMessages: API conversation history file not found for taskId: ${taskId}. Expected at: ${filePath}`, + ) return [] } From 9d19c687f1e92cec0270cba06a1a0b5be5d4ab88 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Tue, 3 Jun 2025 22:10:19 -0700 Subject: [PATCH 2/2] fix: wrap JSON.parse calls in try/catch blocks Add error handling for JSON parsing failures in readApiMessages: - Wrap JSON.parse calls in try/catch blocks for both current and legacy history files - Add detailed error logging with taskId and file paths - Return empty arrays when parsing fails to maintain function contract - Improve existing debug logging for empty history arrays - Handle file unlinking even when parsing fails This prevents crashes from malformed JSON in history files and provides better debugging information when API conversation history is corrupted. Related to issue #4311. Signed-off-by: Eric Wheeler --- src/core/task-persistence/apiMessages.ts | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/core/task-persistence/apiMessages.ts b/src/core/task-persistence/apiMessages.ts index 01d9889d312..d6c17bd9b3d 100644 --- a/src/core/task-persistence/apiMessages.ts +++ b/src/core/task-persistence/apiMessages.ts @@ -22,26 +22,41 @@ export async function readApiMessages({ if (await fileExistsAtPath(filePath)) { const fileContent = await fs.readFile(filePath, "utf8") - const parsedData = JSON.parse(fileContent) - if (Array.isArray(parsedData) && parsedData.length === 0) { + try { + const parsedData = JSON.parse(fileContent) + if (Array.isArray(parsedData) && parsedData.length === 0) { + console.error( + `[Roo-Debug] readApiMessages: Found API conversation history file, but it's empty (parsed as []). TaskId: ${taskId}, Path: ${filePath}`, + ) + } + return parsedData + } catch (error) { console.error( - `[Roo-Debug] readApiMessages: Found API conversation history file, but it's empty. TaskId: ${taskId}, Path: ${filePath}`, + `[Roo-Debug] readApiMessages: Error parsing API conversation history file. TaskId: ${taskId}, Path: ${filePath}, Error: ${error}`, ) + throw error } - return parsedData } else { const oldPath = path.join(taskDir, "claude_messages.json") if (await fileExistsAtPath(oldPath)) { const fileContent = await fs.readFile(oldPath, "utf8") - const parsedData = JSON.parse(fileContent) - if (Array.isArray(parsedData) && parsedData.length === 0) { + try { + const parsedData = JSON.parse(fileContent) + if (Array.isArray(parsedData) && parsedData.length === 0) { + console.error( + `[Roo-Debug] readApiMessages: Found OLD API conversation history file (claude_messages.json), but it's empty (parsed as []). TaskId: ${taskId}, Path: ${oldPath}`, + ) + } + await fs.unlink(oldPath) + return parsedData + } catch (error) { console.error( - `[Roo-Debug] readApiMessages: Found OLD API conversation history file (claude_messages.json), but it's empty. TaskId: ${taskId}, Path: ${oldPath}`, + `[Roo-Debug] readApiMessages: Error parsing OLD API conversation history file (claude_messages.json). TaskId: ${taskId}, Path: ${oldPath}, Error: ${error}`, ) + // DO NOT unlink oldPath if parsing failed, throw error instead. + throw error } - await fs.unlink(oldPath) - return parsedData } }