From 096b6aec4dccded550fce595a1928e1815c3239a Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Tue, 30 Dec 2025 14:14:04 -0800 Subject: [PATCH] fix: properly serialize error objects in transcript logging (#427) Co-authored-by: Letta --- src/cli/App.tsx | 12 ++++++++++-- src/cli/helpers/accumulator.ts | 13 ++++++++++--- src/cli/helpers/errorFormatter.ts | 24 +++++++++++++++++++++++- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index d91d1d3..ef55a38 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -945,18 +945,26 @@ export default function App({ // Also tracks the error in telemetry so we know an error was shown const appendError = useCallback( (message: string, skipTelemetry = false) => { + // Defensive: ensure message is always a string (guards against [object Object]) + const text = + typeof message === "string" + ? message + : message != null + ? JSON.stringify(message) + : "[Unknown error]"; + const id = uid("err"); buffersRef.current.byId.set(id, { kind: "error", id, - text: message, + text, }); buffersRef.current.order.push(id); refreshDerived(); // Track error in telemetry (unless explicitly skipped for user-initiated actions) if (!skipTelemetry) { - telemetry.trackError("ui_error", message, "error_display", { + telemetry.trackError("ui_error", text, "error_display", { modelId: currentModelId || undefined, }); } diff --git a/src/cli/helpers/accumulator.ts b/src/cli/helpers/accumulator.ts index 83a904d..47fd58e 100644 --- a/src/cli/helpers/accumulator.ts +++ b/src/cli/helpers/accumulator.ts @@ -420,12 +420,19 @@ export function onChunk(b: Buffers, chunk: LettaStreamingResponse) { for (const toolReturn of toolReturns) { const toolCallId = toolReturn.tool_call_id; // Handle both func_response (streaming) and tool_return (SDK) properties - const resultText = + const rawResult = ("func_response" in toolReturn ? toolReturn.func_response : undefined) || - ("tool_return" in toolReturn ? toolReturn.tool_return : undefined) || - ""; + ("tool_return" in toolReturn ? toolReturn.tool_return : undefined); + + // Ensure resultText is always a string (guard against SDK returning objects) + const resultText = + typeof rawResult === "string" + ? rawResult + : rawResult != null + ? JSON.stringify(rawResult) + : ""; const status = toolReturn.status; // Look up the line by toolCallId diff --git a/src/cli/helpers/errorFormatter.ts b/src/cli/helpers/errorFormatter.ts index 5c0fe43..861f8bb 100644 --- a/src/cli/helpers/errorFormatter.ts +++ b/src/cli/helpers/errorFormatter.ts @@ -98,7 +98,29 @@ export function formatErrorDetails(e: unknown, agentId?: string): string { return e.message; } - // Fallback for any other type + // Fallback for any other type (e.g., plain objects thrown by SDK or other code) + if (typeof e === "object" && e !== null) { + const obj = e as Record; + + // Check common error-like properties + if (typeof obj.message === "string") { + return obj.message; + } + if (typeof obj.error === "string") { + return obj.error; + } + if (typeof obj.detail === "string") { + return obj.detail; + } + + // Last resort: JSON stringify + try { + return JSON.stringify(e, null, 2); + } catch { + return "[Error: Unable to serialize error object]"; + } + } + return String(e); }