From e659f0bdde9642e6d63c57ff0a534f6f8515accf Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:48:36 +0530 Subject: [PATCH] Log background agent failure reasons --- apps/x/ANALYTICS.md | 3 +- .../core/src/background-tasks/runner.ts | 29 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/x/ANALYTICS.md b/apps/x/ANALYTICS.md index 5bc0f456..6c8dec08 100644 --- a/apps/x/ANALYTICS.md +++ b/apps/x/ANALYTICS.md @@ -181,7 +181,8 @@ All renderer events live in `apps/renderer/src/lib/analytics.ts` (typed wrappers - `email_send_failed` — send returned an error or threw (`components/email-view.tsx`) - `meeting_summarize_failed` — post-recording notes generation threw (`App.tsx`) -- `bg_agent_run_failed` / `bg_agent_run_completed` — `{ trigger: 'manual' | 'cron' | 'window' | 'event' }` **(core)** — every background-agent run settles as exactly one of these (`packages/core/src/background-tasks/runner.ts`), giving a failure *rate* across all trigger sources, not just manual clicks +- `bg_agent_run_failed` — `{ trigger: 'manual' | 'cron' | 'window' | 'event', error: string }` **(core)** — emitted when a background-agent run fails; `error` contains the normalized failure message +- `bg_agent_run_completed` — `{ trigger: 'manual' | 'cron' | 'window' | 'event' }` **(core)** — emitted when a background-agent run succeeds; together these events give a failure *rate* across all trigger sources, not just manual clicks **Misc**: diff --git a/apps/x/packages/core/src/background-tasks/runner.ts b/apps/x/packages/core/src/background-tasks/runner.ts index 73a94bc7..49fda498 100644 --- a/apps/x/packages/core/src/background-tasks/runner.ts +++ b/apps/x/packages/core/src/background-tasks/runner.ts @@ -93,6 +93,10 @@ Your task folder is \`${wsFolder}\`. The user-visible artifact is \`${wsFolder}i const runningTasks = new Set(); +type RunAnalyticsOutcome = + | { event: 'bg_agent_run_completed'; properties: { trigger: BackgroundTaskTriggerType } } + | { event: 'bg_agent_run_failed'; properties: { trigger: BackgroundTaskTriggerType; error: string } }; + // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- @@ -114,6 +118,8 @@ export async function runBackgroundTask( } runningTasks.add(slug); + let analyticsOutcome: RunAnalyticsOutcome | undefined; + try { const task = await fetchTask(slug); if (!task) { @@ -211,7 +217,6 @@ export async function runBackgroundTask( }); log.log(`${slug} — done summary="${truncate(summary)}"`); - capture('bg_agent_run_completed', { trigger }); backgroundTaskBus.publish({ type: 'background_task_agent_complete', @@ -220,10 +225,16 @@ export async function runBackgroundTask( ...(summary ? { summary } : {}), }); + analyticsOutcome = { event: 'bg_agent_run_completed', properties: { trigger } }; return { slug, runId, summary }; } catch (err) { const msg = err instanceof Error ? err.message : String(err); + analyticsOutcome = { + event: 'bg_agent_run_failed', + properties: { trigger, error: msg }, + }; + // Failure — only record the error. `lastRunAt` and `lastRunSummary` // are deliberately untouched so the user keeps seeing the last good // state; the scheduler's backoff (lastAttemptAt + 5min) prevents @@ -235,7 +246,6 @@ export async function runBackgroundTask( } log.log(`${slug} — failed: ${truncate(msg)}`); - capture('bg_agent_run_failed', { trigger }); backgroundTaskBus.publish({ type: 'background_task_agent_complete', @@ -246,7 +256,22 @@ export async function runBackgroundTask( return { slug, runId, summary: null, error: msg }; } + } catch (err) { + // Preserve the original throw behavior for setup/infrastructure errors, + // but still settle analytics for the attempted run. If the agent had + // already failed, keep that original failure reason. + analyticsOutcome ??= { + event: 'bg_agent_run_failed', + properties: { + trigger, + error: err instanceof Error ? err.message : String(err), + }, + }; + throw err; } finally { + if (analyticsOutcome) { + capture(analyticsOutcome.event, analyticsOutcome.properties); + } runningTasks.delete(slug); } }