# Modly Error Handling Strategy for Uncaught Exceptions in Main and Renderer Processes

> Discover Modly's error handling strategy for uncaught exceptions. Learn how Modly logs and displays errors from main and renderer processes via IPC and toast notifications.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: best-practices
- Published: 2026-08-20

---

**Modly catches uncaught exceptions and unhandled promise rejections in the Electron main process using `process.on('uncaughtException')` and `process.on('unhandledRejection')`, logs them via a built-in logger, and forwards stringified error messages to the renderer through an IPC channel where the UI displays them as toast notifications.**

Modly is an Electron-based application that combines a Node.js main process with a Chromium-based renderer process. Understanding how Modly handles uncaught exceptions across both process types is essential for developing robust desktop applications and debugging production failures. This article examines the specific error handling implementation found in the `lightningpixel/modly` repository.

## Main Process Error Handling

The main process in Modly serves as the application's backbone: it starts the app, creates the browser window, launches the Python backend, and manages communication with the renderer. Because unhandled errors here can crash the entire application, Modly registers global error handlers immediately at startup.

### Registering Global Exception Handlers

In [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts), Modly attaches two critical event listeners to the Node.js `process` object:

```typescript
// electron/main/index.ts
process.on('uncaughtException', (err) => {
  if ((err as NodeJS.ErrnoException).code === 'EPIPE') return;
  logger.error(`Uncaught exception: ${err.stack ?? err.message}`);
  mainWindow?.webContents.send('app:error', err.stack ?? err.message);
});

process.on('unhandledRejection', (reason) => {
  const msg = String(reason);
  logger.error(`Unhandled rejection: ${msg}`);
  mainWindow?.webContents.send('app:error', msg);
});

```

**Three key behaviors** define this handler:

- **EPIPE filtering**: Broken-pipe errors are silently ignored. These occur when the launching terminal closes and would otherwise trigger an endless loop of uncaught exception events.
- **Structured logging**: The `logger.error` call persists errors to Modly's centralized log, making them available for later analysis.
- **Renderer forwarding**: Errors are stringified and sent to the renderer via `webContents.send('app:error', …)`, ensuring the user receives visual feedback even when the main process encounters fatal errors.

### Logger Implementation

The main process relies on a dedicated logger module located at [`electron/main/logger.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/logger.ts). While the analysis does not expose its full implementation, the usage pattern suggests a simple wrapper around standard logging with potential file output for persistent error records.

## Renderer Process Error Handling

The renderer process runs Modly's React + TypeScript UI inside a sandboxed Chromium environment. It cannot directly access Node.js exception handlers, so it depends entirely on IPC messages from the main process for centralized error reporting.

### Receiving and Displaying Errors

In [`src/renderer/errorListener.ts`](https://github.com/lightningpixel/modly/blob/main/src/renderer/errorListener.ts), Modly registers an IPC listener that bridges main-process errors to the UI layer:

```typescript
// src/renderer/errorListener.ts
import { ipcRenderer } from 'electron';
import { showErrorToast } from './ui/toast';

ipcRenderer.on('app:error', (_event, msg: string) => {
  console.error('Renderer received uncaught error:', msg);
  showErrorToast(msg);          // UI component that displays a modal/toast
});

```

This listener performs three operations:

1. Logs the received error to the browser console for developer inspection
2. Invokes `showErrorToast()` to present a human-readable error dialog to the user
3. Provides a hook for telemetry or error-tracking services to capture the failure

### Toast Notification Implementation

The actual visual presentation is handled by [`src/renderer/ui/toast.tsx`](https://github.com/lightningpixel/modly/blob/main/src/renderer/ui/toast.tsx), which wraps the `react-hot-toast` library:

```tsx
// src/renderer/ui/toast.tsx
import React from 'react';
import { toast } from 'react-hot-toast';

export const showErrorToast = (msg: string) => {
  toast.error(msg, { duration: 8000 });
};

```

The **8-second duration** ensures users have adequate time to read stack traces or error details, while the toast pattern prevents modal dialogs from blocking the entire interface.

## Error Flow Architecture

Modly's error handling strategy follows a **unidirectional flow**:

1. Exception occurs in main process (Node.js)
2. Global handler catches and filters the error (EPIPE exclusion)
3. Error is logged to [`electron/main/logger.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/logger.ts)
4. Stringified message travels via `app:error` IPC channel
5. Renderer listener receives and console-logs the error
6. `showErrorToast()` renders visible feedback to the user

This architecture centralizes error handling while maintaining separation of concerns: the main process manages logging and inter-process communication, while the renderer owns user-facing presentation.

## Debugging and Development Utilities

Modly includes a development-only utility for testing the error handling pipeline. In [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts), a conditional IPC handler allows manual exception triggering:

```typescript
// Somewhere in electron/main/index.ts – e.g. a dev-only menu item
if (process.env.NODE_ENV === 'development') {
  ipcMain.handle('dev:throw', () => {
    throw new Error('Test uncaught exception');
  });
}

```

Developers can invoke this handler to verify that errors propagate correctly through the logging and UI notification systems before deploying to production.

## Key Implementation Files

| File | Responsibility |
|------|--------------|
| [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) | Registers `uncaughtException` and `unhandledRejection` handlers; forwards errors to renderer |
| [`electron/main/logger.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/logger.ts) | Provides centralized logging for main-process operations |
| [`src/renderer/errorListener.ts`](https://github.com/lightningpixel/modly/blob/main/src/renderer/errorListener.ts) | IPC listener bridging main-process errors to renderer state |
| [`src/renderer/ui/toast.tsx`](https://github.com/lightningpixel/modly/blob/main/src/renderer/ui/toast.tsx) | React component rendering error notifications via `react-hot-toast` |

## Comparison: Main vs. Renderer Error Handling

| Aspect | Main Process | Renderer Process |
|--------|-----------|------------------|
| **Entry point** | [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) | [`src/renderer/errorListener.ts`](https://github.com/lightningpixel/modly/blob/main/src/renderer/errorListener.ts) |
| **Hook mechanism** | `process.on('uncaughtException')` | `ipcRenderer.on('app:error')` |
| **Logging target** | `logger.error()` | `console.error()` |
| **User feedback** | Indirect (via IPC) | Direct (`showErrorToast()`) |
| **Special handling** | EPIPE filtering | None (receives pre-filtered errors) |

## Summary

- **Modly error handling** relies on Node.js global exception handlers in the main process, with intentional filtering of broken-pipe errors to prevent infinite loops.
- All caught errors are **logged centrally** and **forwarded to the renderer** via a dedicated `app:error` IPC channel.
- The **renderer displays errors as persistent toast notifications**, giving users clear feedback while preserving application stability.
- The architecture separates concerns effectively: logging and filtering occur in the main process, while presentation logic lives in the renderer.
- **Source file locations** include [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) for handler registration and [`src/renderer/errorListener.ts`](https://github.com/lightningpixel/modly/blob/main/src/renderer/errorListener.ts) for UI bridging.

## Frequently Asked Questions

### How does Modly prevent broken-pipe errors from crashing the application?

Modly explicitly checks for the `EPIPE` error code in the `uncaughtException` handler and returns early without logging or forwarding. This prevents an endless cascade of exceptions when the parent terminal detaches, a common scenario in Electron applications launched from command-line interfaces.

### Can renderer-process JavaScript errors trigger the same toast notifications?

The current implementation only displays errors forwarded from the main process. Renderer-native uncaught exceptions would require additional handlers—such as `window.onerror` or React Error Boundaries—to achieve equivalent visibility, as these do not automatically propagate through Electron's IPC system.

### What logger does Modly use for main-process errors?

Modly employs a custom logger defined in [`electron/main/logger.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/logger.ts) rather than direct `console` calls. This abstraction enables consistent formatting, potential file-based persistence, and future extensibility for structured logging or remote aggregation services.

### Is the error toast duration configurable?

Yes. The `showErrorToast` function in [`src/renderer/ui/toast.tsx`](https://github.com/lightningpixel/modly/blob/main/src/renderer/ui/toast.tsx) passes an explicit `duration: 8000` (milliseconds) to `react-hot-toast`. Developers can modify this value or make it dynamic based on error severity by extending the function's parameter signature.