From b5ce59d059f76dcd58d367589117c3f2540e26fb Mon Sep 17 00:00:00 2001 From: cthomas Date: Tue, 23 Dec 2025 16:15:09 -0800 Subject: [PATCH] chore: make error logging more robust (#373) --- src/cli/App.tsx | 41 +++++++++++++++++++++++++++++++++++------ src/telemetry/index.ts | 3 +++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index ce07169..4318aaf 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -844,8 +844,9 @@ export default function App({ }, [loadingState, agentId]); // Helper to append an error to the transcript + // Also tracks the error in telemetry so we know an error was shown const appendError = useCallback( - (message: string) => { + (message: string, skipTelemetry = false) => { const id = uid("err"); buffersRef.current.byId.set(id, { kind: "error", @@ -854,8 +855,15 @@ export default function App({ }); 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", { + modelId: currentModelId || undefined, + }); + } }, - [refreshDerived], + [refreshDerived, currentModelId], ); // Core streaming function - iterative loop that processes conversation turns @@ -876,6 +884,9 @@ export default function App({ } processingConversationRef.current += 1; + // Track last run ID for error reporting (accessible in catch block) + let currentRunId: string | undefined; + try { // Check if user hit escape before we started if (userCancelledRef.current) { @@ -977,6 +988,9 @@ export default function App({ syncAgentState, ); + // Update currentRunId for error reporting in catch block + currentRunId = lastRunId ?? undefined; + // Track API duration sessionStatsRef.current.endTurn(apiDurationMs); sessionStatsRef.current.updateUsageFromBuffers(buffersRef.current); @@ -1044,7 +1058,7 @@ export default function App({ } else { // Regular user cancellation - show error if (!EAGER_CANCEL) { - appendError("Stream interrupted by user"); + appendError("Stream interrupted by user", true); } } @@ -1389,6 +1403,17 @@ export default function App({ // Mark incomplete tool calls as finished to prevent stuck blinking UI markIncompleteToolsAsCancelled(buffersRef.current); + // Track the server-side error in telemetry + telemetry.trackError( + stopReason || "unknown_stop_reason", + `Stream stopped with reason: ${stopReason}`, + "message_stream", + { + modelId: currentModelId || undefined, + runId: lastRunId ?? undefined, + }, + ); + // Fetch error details from the run if available if (lastRunId) { try { @@ -1414,17 +1439,19 @@ export default function App({ errorObject, agentIdRef.current, ); - appendError(errorDetails); + appendError(errorDetails, true); // Skip telemetry - already tracked above } else { // No error metadata, show generic error with run info appendError( `An error occurred during agent execution\n(run_id: ${lastRunId}, stop_reason: ${stopReason})`, + true, // Skip telemetry - already tracked above ); } } catch (_e) { // If we can't fetch error details, show generic error appendError( `An error occurred during agent execution\n(run_id: ${lastRunId}, stop_reason: ${stopReason})\n(Unable to fetch additional error details from server)`, + true, // Skip telemetry - already tracked above ); return; } @@ -1432,6 +1459,7 @@ export default function App({ // No run_id available - but this is unusual since errors should have run_ids appendError( `An error occurred during agent execution\n(stop_reason: ${stopReason})`, + true, // Skip telemetry - already tracked above ); } @@ -1468,11 +1496,12 @@ export default function App({ telemetry.trackError(errorType, errorMessage, "message_stream", { httpStatus, modelId: currentModelId || undefined, + runId: currentRunId, }); // Use comprehensive error formatting const errorDetails = formatErrorDetails(e, agentIdRef.current); - appendError(errorDetails); + appendError(errorDetails, true); // Skip telemetry - already tracked above with more context setStreaming(false); refreshDerived(); } finally { @@ -1552,7 +1581,7 @@ export default function App({ setStreaming(false); const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current); if (!toolsCancelled) { - appendError("Stream interrupted by user"); + appendError("Stream interrupted by user", true); } refreshDerived(); diff --git a/src/telemetry/index.ts b/src/telemetry/index.ts index 54d992a..008961e 100644 --- a/src/telemetry/index.ts +++ b/src/telemetry/index.ts @@ -43,6 +43,7 @@ export interface ErrorData { context?: string; http_status?: number; model_id?: string; + run_id?: string; } export interface UserInputData { @@ -340,6 +341,7 @@ class TelemetryManager { options?: { httpStatus?: number; modelId?: string; + runId?: string; }, ) { const data: ErrorData = { @@ -348,6 +350,7 @@ class TelemetryManager { context, http_status: options?.httpStatus, model_id: options?.modelId, + run_id: options?.runId, }; this.track("error", data); }