From c39bda9a0121766c77664742c7b27b5f8ce30c53 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Thu, 11 Dec 2025 22:30:26 -0800 Subject: [PATCH] fix: improve error handling for headless (#187) --- src/headless.ts | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/headless.ts b/src/headless.ts index 981d224..63f4273 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -1,5 +1,4 @@ import { parseArgs } from "node:util"; -import { APIError } from "@letta-ai/letta-client/core/error"; import type { AgentState, MessageCreate, @@ -19,6 +18,7 @@ import { markIncompleteToolsAsCancelled, toLines, } from "./cli/helpers/accumulator"; +import { formatErrorDetails } from "./cli/helpers/errorFormatter"; import { safeJsonParseOr } from "./cli/helpers/safeJsonParse"; import { drainStreamWithResume } from "./cli/helpers/stream"; import { settingsManager } from "./settings-manager"; @@ -846,20 +846,24 @@ export async function handleHeadlessCommand( ? errorMessages.join("; ") : `Unexpected stop reason: ${stopReason}`; - // Fetch detailed error from run metadata if available + // Fetch detailed error from run metadata if available (same as TUI mode) if (lastRunId && errorMessages.length === 0) { try { const run = await client.runs.retrieve(lastRunId); if (run.metadata?.error) { - const error = run.metadata.error as { + const errorData = run.metadata.error as { type?: string; message?: string; detail?: string; }; - const errorType = error.type ? `[${error.type}] ` : ""; - const errorMsg = error.message || "An error occurred"; - const errorDetail = error.detail ? `: ${error.detail}` : ""; - errorMessage = `${errorType}${errorMsg}${errorDetail}`; + // Construct error object that formatErrorDetails can parse + const errorObject = { + error: { + error: errorData, + run_id: lastRunId, + }, + }; + errorMessage = formatErrorDetails(errorObject, agent.id); } } catch (_e) { // If we can't fetch error details, append note to error message @@ -878,11 +882,7 @@ export async function handleHeadlessCommand( }), ); } else { - // Include run_id and stop_reason for debugging - const runInfoSuffix = lastRunId - ? ` (run_id: ${lastRunId}, stop_reason: ${stopReason})` - : ` (stop_reason: ${stopReason})`; - console.error(`${errorMessage}${runInfoSuffix}`); + console.error(`Error: ${errorMessage}`); } process.exit(1); } @@ -890,23 +890,19 @@ export async function handleHeadlessCommand( // Mark incomplete tool calls as cancelled markIncompleteToolsAsCancelled(buffers); - // Build run info suffix for debugging - const runInfoSuffix = lastKnownRunId - ? ` (run_id: ${lastKnownRunId}, stop_reason: error)` - : ""; + // Use comprehensive error formatting (same as TUI mode) + const errorDetails = formatErrorDetails(error, agent.id); - // Handle APIError from streaming (event: error) - if (error instanceof APIError && error.error?.error) { - const { type, message, detail } = error.error.error; - const errorType = type ? `[${type}] ` : ""; - const errorMessage = message || "An error occurred"; - const errorDetail = detail ? `: ${detail}` : ""; - console.error( - `Error: ${errorType}${errorMessage}${errorDetail}${runInfoSuffix}`, + if (outputFormat === "stream-json") { + console.log( + JSON.stringify({ + type: "error", + message: errorDetails, + run_id: lastKnownRunId, + }), ); } else { - // Fallback for non-API errors - console.error(`Error: ${error}${runInfoSuffix}`); + console.error(`Error: ${errorDetails}`); } process.exit(1); }