From da5551fea52614f8db6e2ee588215ab7c902a94c Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Wed, 4 Feb 2026 11:41:15 -0600 Subject: [PATCH] Add Sentry breadcrumbs and memory monitoring for crash diagnostics - Add addBreadcrumb() utility to track user actions before crashes - Add startMemoryMonitoring() with configurable threshold and interval - Record breadcrumbs at agent spawn/kill operations - Auto-start memory monitoring when Sentry initializes (500MB threshold, 1min interval) This provides context in crash reports to help identify patterns leading to renderer crashes (MAESTRO-5A/4Y). Relates to MAESTRO-5A, MAESTRO-4Y --- src/main/index.ts | 6 ++ src/main/ipc/handlers/process.ts | 11 +++ src/main/utils/sentry.ts | 116 +++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/src/main/index.ts b/src/main/index.ts index 3208659b..930981b6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -191,6 +191,12 @@ if (crashReportingEnabled && !isDevelopment) { }); // Add installation ID to Sentry for error correlation across installations setTag('installationId', installationId); + + // Start memory monitoring for crash diagnostics (MAESTRO-5A/4Y) + // Records breadcrumbs with memory state every minute, warns above 500MB heap + import('./utils/sentry').then(({ startMemoryMonitoring }) => { + startMemoryMonitoring(500, 60000); + }); }) .catch((err) => { logger.warn('Failed to initialize Sentry', 'Startup', { error: String(err) }); diff --git a/src/main/ipc/handlers/process.ts b/src/main/ipc/handlers/process.ts index 900e5658..35bec273 100644 --- a/src/main/ipc/handlers/process.ts +++ b/src/main/ipc/handlers/process.ts @@ -4,6 +4,7 @@ import * as os from 'os'; import { ProcessManager } from '../../process-manager'; import { AgentDetector } from '../../agents'; import { logger } from '../../utils/logger'; +import { addBreadcrumb } from '../../utils/sentry'; import { isWebContentsAvailable } from '../../utils/safe-send'; import { buildAgentArgs, @@ -242,6 +243,14 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void }), }); + // Add breadcrumb for crash diagnostics (MAESTRO-5A/4Y) + await addBreadcrumb('agent', `Spawn: ${config.toolType}`, { + sessionId: config.sessionId, + toolType: config.toolType, + command: config.command, + hasPrompt: !!config.prompt, + }); + // Get contextWindow: session-level override takes priority over agent-level config // Falls back to the agent's configOptions default (e.g., 400000 for Codex, 128000 for OpenCode) const contextWindow = getContextWindowValue( @@ -498,6 +507,8 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void withIpcErrorLogging(handlerOpts('kill'), async (sessionId: string) => { const processManager = requireProcessManager(getProcessManager); logger.info(`Killing process: ${sessionId}`, LOG_CONTEXT, { sessionId }); + // Add breadcrumb for crash diagnostics (MAESTRO-5A/4Y) + await addBreadcrumb('agent', `Kill: ${sessionId}`, { sessionId }); return processManager.kill(sessionId); }) ); diff --git a/src/main/utils/sentry.ts b/src/main/utils/sentry.ts index f354c149..cbe5ebc0 100644 --- a/src/main/utils/sentry.ts +++ b/src/main/utils/sentry.ts @@ -10,6 +10,16 @@ import { logger } from './logger'; /** Sentry severity levels */ export type SentrySeverityLevel = 'fatal' | 'error' | 'warning' | 'log' | 'info' | 'debug'; +/** Breadcrumb categories for tracking user actions before crashes */ +export type BreadcrumbCategory = + | 'session' + | 'agent' + | 'navigation' + | 'ui' + | 'ipc' + | 'memory' + | 'file'; + /** Sentry module type for crash reporting */ interface SentryModule { captureMessage: ( @@ -20,6 +30,12 @@ interface SentryModule { exception: Error | unknown, captureContext?: { level?: SentrySeverityLevel; extra?: Record } ) => string; + addBreadcrumb: (breadcrumb: { + category?: string; + message?: string; + level?: SentrySeverityLevel; + data?: Record; + }) => void; } /** Cached Sentry module reference */ @@ -72,3 +88,103 @@ export async function captureMessage( logger.debug('Sentry not available for message reporting', '[Sentry]'); } } + +/** + * Adds a breadcrumb to track user actions for crash diagnostics. + * Breadcrumbs are recorded and sent with crash reports to help identify + * what the user was doing before a crash occurred. + * + * Use this at key interaction points: + * - Session switches, creates, deletes + * - Agent spawns/kills + * - Heavy UI operations (document graph, large history loads) + * - File operations + * + * @param category - Category of the action (session, agent, navigation, ui, ipc, memory, file) + * @param message - Brief description of the action + * @param data - Optional additional context data + * @param level - Severity level (default: 'info') + */ +export async function addBreadcrumb( + category: BreadcrumbCategory, + message: string, + data?: Record, + level: SentrySeverityLevel = 'info' +): Promise { + try { + if (!sentryModule) { + const sentry = await import('@sentry/electron/main'); + sentryModule = sentry; + } + sentryModule.addBreadcrumb({ + category, + message, + level, + data, + }); + } catch { + // Sentry not available - silently ignore + } +} + +/** + * Memory monitoring interval ID for cleanup + */ +let memoryMonitorInterval: NodeJS.Timeout | null = null; + +/** + * Starts periodic memory monitoring that adds breadcrumbs when memory usage is high. + * This helps diagnose crashes that may be related to memory pressure. + * + * @param thresholdMB - Memory threshold in MB above which to log warnings (default: 500MB) + * @param intervalMs - Check interval in milliseconds (default: 60000 = 1 minute) + */ +export function startMemoryMonitoring(thresholdMB: number = 500, intervalMs: number = 60000): void { + // Don't start if already running + if (memoryMonitorInterval) { + return; + } + + memoryMonitorInterval = setInterval(async () => { + const memUsage = process.memoryUsage(); + const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024); + const heapTotalMB = Math.round(memUsage.heapTotal / 1024 / 1024); + const rssMB = Math.round(memUsage.rss / 1024 / 1024); + + // Always add a breadcrumb with current memory state + await addBreadcrumb('memory', `Memory: ${heapUsedMB}MB heap, ${rssMB}MB RSS`, { + heapUsedMB, + heapTotalMB, + rssMB, + externalMB: Math.round(memUsage.external / 1024 / 1024), + }); + + // Log warning if heap usage exceeds threshold + if (heapUsedMB > thresholdMB) { + logger.warn(`High memory usage: ${heapUsedMB}MB heap (threshold: ${thresholdMB}MB)`, 'Memory', { + heapUsedMB, + heapTotalMB, + rssMB, + }); + await addBreadcrumb( + 'memory', + `HIGH MEMORY: ${heapUsedMB}MB exceeds ${thresholdMB}MB threshold`, + { heapUsedMB, heapTotalMB, rssMB }, + 'warning' + ); + } + }, intervalMs); + + logger.info(`Memory monitoring started (threshold: ${thresholdMB}MB, interval: ${intervalMs}ms)`, 'Memory'); +} + +/** + * Stops the memory monitoring interval. + */ +export function stopMemoryMonitoring(): void { + if (memoryMonitorInterval) { + clearInterval(memoryMonitorInterval); + memoryMonitorInterval = null; + logger.info('Memory monitoring stopped', 'Memory'); + } +}