From c81d3cb27b73e1b485a9ade643ac1f794f1b73a2 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:36:00 +0530 Subject: [PATCH] surface silent runtime failures as error events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AgentRuntime.trigger() wrapped its body in try/finally with no outer catch. An inner catch around the streamAgent for-await only handled AbortError and rethrew everything else. Call sites fire-and-forget trigger (runs.ts:26,60,72), so any thrown error became an unhandled promise rejection. The finally still ran and published run-processing-end, but nothing told the renderer why — the chat showed the spinner, then an empty assistant bubble. Provider misconfig, invalid API keys, unknown model ids, streamText setup throws, runsRepo.fetch or loadAgent failing, and provider auth/rate-limit rejections on the first chunk all hit this path on a first message. All invisible. Add a top-level catch that formats the error to a string and emits a {type: "error"} RunEvent via the existing runsRepo/bus path. The renderer already renders those as a chat bubble plus toast (App.tsx:2069) — no UI work needed. No changes to the abort path: user-initiated stops still flow through the existing inner catch and the signal.aborted branch that emits run-stopped. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/x/packages/core/src/agents/runtime.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/x/packages/core/src/agents/runtime.ts b/apps/x/packages/core/src/agents/runtime.ts index f978449b..81421358 100644 --- a/apps/x/packages/core/src/agents/runtime.ts +++ b/apps/x/packages/core/src/agents/runtime.ts @@ -194,6 +194,19 @@ export class AgentRuntime implements IAgentRuntime { await this.runsRepo.appendEvents(runId, [stoppedEvent]); await this.bus.publish(stoppedEvent); } + } catch (error) { + console.error(`Run ${runId} failed:`, error); + const message = error instanceof Error + ? (error.stack || error.message || error.name) + : typeof error === "string" ? error : JSON.stringify(error); + const errorEvent: z.infer = { + runId, + type: "error", + error: message, + subflow: [], + }; + await this.runsRepo.appendEvents(runId, [errorEvent]); + await this.bus.publish(errorEvent); } finally { this.abortRegistry.cleanup(runId); await this.runsLock.release(runId);