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
This commit is contained in:
Pedram Amini
2026-02-04 11:41:15 -06:00
parent a761b84e77
commit da5551fea5
3 changed files with 133 additions and 0 deletions

View File

@@ -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) });

View File

@@ -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);
})
);

View File

@@ -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, unknown> }
) => string;
addBreadcrumb: (breadcrumb: {
category?: string;
message?: string;
level?: SentrySeverityLevel;
data?: Record<string, unknown>;
}) => 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<string, unknown>,
level: SentrySeverityLevel = 'info'
): Promise<void> {
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');
}
}