# How Modly Notifies the Renderer Process About Python Backend Crashes via IPC

> Learn how Modly notifies the renderer process about Python backend crashes. It uses IPC and an exit listener on the FastAPI child process to send a python:crashed message.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: internals
- Published: 2026-08-21

---

**Modly broadcasts Python backend crashes to the Electron renderer by registering an `exit` listener on the FastAPI child process in [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts), then sending a `python:crashed` IPC message via `webContents.send()` when an unexpected termination is detected.**

Managing a Python FastAPI backend within an Electron application requires reliable inter-process communication (IPC) to handle failures gracefully. In the `lightningpixel/modly` repository, the main process detects when the Python child process terminates unexpectedly and notifies the renderer through a dedicated IPC channel. This mechanism allows the React-based UI to display error toasts, attempt restarts, or enter a degraded state when the backend becomes unavailable.

## Python Bridge Architecture and Process Spawning

The `PythonBridge` class in [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts) manages the lifecycle of the FastAPI server as a Node.js `ChildProcess`. When the bridge initializes, it spawns the Python executable and maintains a reference to the process instance.

This architecture separates the Python backend from the renderer, requiring the main process to act as an intermediary. The bridge tracks process state through an internal `ready` flag and an `intentionalStop` boolean that distinguishes between user-requested shutdowns and actual crashes.

## Detecting Unexpected Terminations in the Main Process

To identify genuine crashes, the bridge registers an **`exit`** event listener on the spawned process. The listener validates both the `ready` state and the `intentionalStop` flag before declaring a crash event.

When the Python process exits, the handler captures the exit code and confirms the termination was unexpected. This prevents false positives during normal application shutdown sequences.

```typescript
this.process.on('exit', (code) => {
  const wasReady = this.ready
  console.log('[PythonBridge] Process exited with code', code)
  this.ready = false
  this.process = null
  if (wasReady && !this.intentionalStop) {
    const win = this.getWindow?.()
    const contents = win?.webContents
    if (contents && !contents.isDestroyed()) {
      // Notify renderer of an unexpected crash
      contents.send('python:crashed', { code })
    }
  }
})

```

## Broadcasting Crashes via IPC to the Renderer

When an unexpected exit is confirmed, the bridge retrieves the `BrowserWindow` instance hosting the renderer and accesses its `webContents` object. It then emits the **`python:crashed`** channel with a payload containing the numeric exit code.

The hard-coded channel name ensures consistent communication across operating systems. The payload structure `{ code }` provides the renderer with diagnostic information while maintaining a minimal API surface.

## Receiving Notifications in the Renderer Process

The preload script at [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) exposes a type-safe API that wraps `ipcRenderer`. This abstraction prevents direct `ipcRenderer` access in the renderer while providing a clean interface for crash subscription.

The `python.onCrashed` method registers a callback that fires whenever the main process broadcasts a crash event. A corresponding `offCrashed` method allows components to clean up listeners during unmount.

```typescript
python: {
  // …other methods
  onCrashed: (cb: (data: { code: number | null }) => void) => {
    ipcRenderer.on('python:crashed', (_event, data) => cb(data as { code: number | null }))
  },
  offCrashed: () => ipcRenderer.removeAllListeners('python:crashed'),
}

```

## Handling Crashes in React Components

Renderer processes typically subscribe to crash notifications within `useEffect` hooks or equivalent lifecycle methods. The API attaches to `window.electron.python`, making it accessible throughout the component tree.

When a crash occurs, the callback receives the exit code and can trigger UI updates such as error toasts, modal dialogs, or automatic restart sequences via the bridge's restart methods.

```typescript
useEffect(() => {
  window.electron.python.onCrashed(({ code }) => {
    // Show an error toast and optionally restart the bridge
    showToast(`Python backend crashed (exit code ${code ?? 'unknown'}).`, 8000)
    // Optionally: window.electron.python.restart()
  })
  return () => window.electron.python.offCrashed()
}, [])

```

## Summary

- **[`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts)** spawns the Python FastAPI process and monitors its `exit` events to detect unexpected terminations.
- The bridge uses an `intentionalStop` flag to filter out deliberate shutdowns from genuine crashes.
- Crashes are broadcast via `webContents.send('python:crashed', { code })` to reach the renderer process.
- **[`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts)** exposes `python.onCrashed()` and `python.offCrashed()` for type-safe subscription management.
- The renderer receives exit codes as `{ code: number | null }`, enabling detailed error reporting and recovery logic in the UI.

## Frequently Asked Questions

### What IPC channel does Modly use for Python backend crash notifications?

Modly uses the **`python:crashed`** channel name, hard-coded in both [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts) and [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts). This consistency ensures the main process and renderer communicate reliably across macOS, Windows, and Linux.

### How does Modly distinguish between intentional shutdowns and crashes?

The `PythonBridge` class maintains an `intentionalStop` boolean flag. When the user or application logic requests a normal shutdown, this flag is set to `true`. The `exit` event listener checks `!this.intentionalStop` before emitting the crash notification, preventing false alerts during routine closures.

### Can the renderer process restart the Python backend after receiving a crash notification?

Yes. While the crash notification itself only transmits the exit code, the preload API typically exposes additional methods such as `python.restart()`. The renderer can invoke these methods within the `onCrashed` callback to attempt automatic recovery without user intervention.

### Where is the crash handling logic implemented in the preload script?

The subscription logic resides in [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) within the `python` object. It wraps `ipcRenderer.on('python:crashed', ...)` inside the `onCrashed` method and provides `offCrashed` to call `ipcRenderer.removeAllListeners`, preventing memory leaks in single-page applications.