fix: better logging on error (#152)

This commit is contained in:
Charles Packer
2025-12-03 22:15:44 -08:00
committed by GitHub
parent bb682b8635
commit 09d120a578
2 changed files with 64 additions and 9 deletions

View File

@@ -680,11 +680,17 @@ export default function App({
initialInput: Array<MessageCreate | ApprovalCreate>,
): Promise<void> => {
const currentInput = initialInput;
// Track lastRunId outside the while loop so it's available in catch block
let lastKnownRunId: string | null = null;
try {
setStreaming(true);
abortControllerRef.current = new AbortController();
// Clear any stale pending tool calls from previous turns
// If we're sending a new message, old pending state is no longer relevant
markIncompleteToolsAsCancelled(buffersRef.current);
while (true) {
// Stream one turn
const stream = await sendMessageStream(agentId, currentInput);
@@ -696,6 +702,11 @@ export default function App({
abortControllerRef.current?.signal,
);
// Update lastKnownRunId for error handling in catch block
if (lastRunId) {
lastKnownRunId = lastRunId;
}
// Track API duration
sessionStatsRef.current.endTurn(apiDurationMs);
sessionStatsRef.current.updateUsageFromBuffers(buffersRef.current);
@@ -902,8 +913,13 @@ export default function App({
// Mark incomplete tool calls as finished to prevent stuck blinking UI
markIncompleteToolsAsCancelled(buffersRef.current);
// Build run info suffix for debugging
const runInfoSuffix = lastRunId
? `\n(run_id: ${lastRunId}, stop_reason: ${stopReason})`
: `\n(stop_reason: ${stopReason})`;
// Fetch error details from the run if available
let errorDetails = `Unexpected stop reason: ${stopReason}`;
let errorDetails = `An error occurred during agent execution`;
if (lastRunId) {
try {
const client = await getClient();
@@ -924,31 +940,43 @@ export default function App({
} catch (_e) {
// If we can't fetch error details, let user know
appendError(
`${errorDetails}\n(Unable to fetch additional error details from server)`,
`${errorDetails}${runInfoSuffix}\n(Unable to fetch additional error details from server)`,
);
return;
}
}
appendError(errorDetails);
appendError(`${errorDetails}${runInfoSuffix}`);
setStreaming(false);
refreshDerived();
return;
}
} catch (e) {
// Mark incomplete tool calls as cancelled to prevent stuck blinking UI
markIncompleteToolsAsCancelled(buffersRef.current);
// Build error message with run_id for debugging
const runIdSuffix = lastKnownRunId
? `\n(run_id: ${lastKnownRunId}, stop_reason: error)`
: "";
// Handle APIError from streaming (event: error)
if (e instanceof APIError && e.error?.error) {
const { type, message, detail } = e.error.error;
const errorType = type ? `[${type}] ` : "";
const errorMessage = message || "An error occurred";
const errorDetail = detail ? `:\n${detail}` : "";
appendError(`${errorType}${errorMessage}${errorDetail}`);
appendError(
`${errorType}${errorMessage}${errorDetail}${runIdSuffix}`,
);
} else {
// Fallback for non-API errors
appendError(e instanceof Error ? e.message : String(e));
const errorMessage = e instanceof Error ? e.message : String(e);
appendError(`${errorMessage}${runIdSuffix}`);
}
setStreaming(false);
refreshDerived();
} finally {
abortControllerRef.current = null;
}

View File

@@ -13,7 +13,11 @@ import { createAgent } from "./agent/create";
import { sendMessageStream } from "./agent/message";
import { getModelUpdateArgs } from "./agent/model";
import { SessionStats } from "./agent/stats";
import { createBuffers, toLines } from "./cli/helpers/accumulator";
import {
createBuffers,
markIncompleteToolsAsCancelled,
toLines,
} from "./cli/helpers/accumulator";
import { safeJsonParseOr } from "./cli/helpers/safeJsonParse";
import { drainStreamWithResume } from "./cli/helpers/stream";
import { settingsManager } from "./settings-manager";
@@ -424,6 +428,9 @@ export async function handleHeadlessCommand(
},
];
// Track lastRunId outside the while loop so it's available in catch block
let lastKnownRunId: string | null = null;
try {
while (true) {
const stream = await sendMessageStream(agent.id, currentInput);
@@ -643,6 +650,7 @@ export async function handleHeadlessCommand(
apiDurationMs = performance.now() - startTime;
// Use the last run_id we saw (if any)
lastRunId = runIds.size > 0 ? Array.from(runIds).pop() || null : null;
if (lastRunId) lastKnownRunId = lastRunId;
// Mark final line as finished
const { markCurrentLineAsFinished } = await import(
@@ -660,6 +668,7 @@ export async function handleHeadlessCommand(
approvals = result.approvals || [];
apiDurationMs = result.apiDurationMs;
lastRunId = result.lastRunId || null;
if (lastRunId) lastKnownRunId = lastRunId;
}
// Track API duration for this stream
@@ -772,6 +781,9 @@ export async function handleHeadlessCommand(
}
// Unexpected stop reason (error, llm_api_error, etc.)
// Mark incomplete tool calls as cancelled to prevent stuck state
markIncompleteToolsAsCancelled(buffers);
// Extract error details from buffers if available
const errorLines = toLines(buffers).filter(
(line) => line.kind === "error",
@@ -813,24 +825,39 @@ export async function handleHeadlessCommand(
type: "error",
message: errorMessage,
stop_reason: stopReason,
run_id: lastRunId,
}),
);
} else {
console.error(errorMessage);
// Include run_id and stop_reason for debugging
const runInfoSuffix = lastRunId
? ` (run_id: ${lastRunId}, stop_reason: ${stopReason})`
: ` (stop_reason: ${stopReason})`;
console.error(`${errorMessage}${runInfoSuffix}`);
}
process.exit(1);
}
} catch (error) {
// Mark incomplete tool calls as cancelled
markIncompleteToolsAsCancelled(buffers);
// Build run info suffix for debugging
const runInfoSuffix = lastKnownRunId
? ` (run_id: ${lastKnownRunId}, stop_reason: error)`
: "";
// 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}`);
console.error(
`Error: ${errorType}${errorMessage}${errorDetail}${runInfoSuffix}`,
);
} else {
// Fallback for non-API errors
console.error(`Error: ${error}`);
console.error(`Error: ${error}${runInfoSuffix}`);
}
process.exit(1);
}