# How to Debug IPC Communication in Orca: A Complete Developer Guide

> Debug IPC communication in Orca by instrumenting the preload bridge, adding logs to ipcMain, and inspecting renderer DevTools. Master IPC debugging with this complete guide for developers.

- Repository: [Stably/orca](https://github.com/stablyai/orca)
- Tags: how-to-guide
- Published: 2026-05-25

---

**To debug IPC communication in Orca, instrument the preload bridge in [`src/preload/index.ts`](https://github.com/stablyai/orca/blob/main/src/preload/index.ts), add logging to `ipcMain.handle` registrations in [`src/main/ipc/pty.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.ts), and use the renderer's DevTools console to inspect `window.api` calls and event listeners.**

Orca is an Electron-based terminal emulator where robust main-process ↔ renderer communication is essential for PTY sessions, filesystem operations, and settings management. Understanding how to trace messages through Orca's typed IPC layers will help you identify where data drops, duplicate listeners form, or environment variables fail to inject. This guide walks through the architectural layers of **debugging IPC communication in Orca** using the actual source paths and handler signatures from the stablyai/orca repository.

## Understanding Orca's IPC Architecture

Orca isolates renderer access through a three-layer IPC stack. Each layer has specific debugging hooks you can instrument without modifying core business logic.

### The Preload Bridge ([`src/preload/index.ts`](https://github.com/stablyai/orca/blob/main/src/preload/index.ts))

The **preload bridge** exposes a safe `window.api` object to the renderer, funneling all calls through `ipcRenderer.invoke` for request-response patterns or `ipcRenderer.on` for events.

```typescript
// src/preload/index.ts
contextBridge.exposeInMainWorld('api', {
  getIdentity: (): Promise<AppIdentity> => ipcRenderer.invoke('app:getIdentity'),
  
  onTerminalFileDrop: (listener) => {
    ipcRenderer.on('terminal:file-drop', listener);
    return () => ipcRenderer.removeListener('terminal:file-drop', listener);
  },
});

```

To debug preload exposure, open the renderer DevTools and execute `console.log(window.api)` to verify which channels are available. You can temporarily expose additional debug namespaces by adding them to this file's `exposeInMainWorld` call.

### Renderer-Side Error Handling ([`src/renderer/src/lib/ipc-error.ts`](https://github.com/stablyai/orca/blob/main/src/renderer/src/lib/ipc-error.ts))

Errors thrown in `ipcMain.handle` get wrapped by Electron into generic `Error` objects. The `extractIpcErrorMessage` helper normalizes these for UI display.

```typescript
// src/renderer/src/lib/ipc-error.ts
export function extractIpcErrorMessage(err: unknown): string {
  if (err instanceof Error) return err.message;
  return String(err);
}

```

When debugging renderer calls, wrap your `window.api` invocations with try-catch blocks that log the raw error object before it passes through this normalizer. This reveals the full stack trace from the main process.

### Main Process Handler Registration ([`src/main/ipc/register-core-handlers.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/register-core-handlers.ts))

All domain-specific handlers (PTY, filesystem, telemetry) register centrally in the `registerCoreHandlers` function. This function includes a guard against double-registration on macOS re-activation.

```typescript
// src/main/ipc/register-core-handlers.ts
export function registerCoreHandlers(
  store: Store,
  runtime: OrcaRuntimeService,
  stats: StatsCollector
) {
  if (registered) return; // Guard against duplicate registration
  registered = true;
  
  registerPtyHandlers(mainWindow, runtime);
  registerFilesystemHandlers(mainWindow);
  // ... additional domains
}

```

Insert `console.log('[IPC] Registering handlers for:', domain)` at the top of each registration function to verify execution order during app startup.

## Debugging the PTY Communication Layer

The PTY channel (`pty:spawn`, `pty:write`, `pty:data`) represents the most complex IPC path in Orca, implementing custom batching to reduce round-trip traffic.

### Handler Registration and Listener Cleanup

In [`src/main/ipc/pty.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.ts), the `registerPtyHandlers` function must clear previous listeners before attaching new ones to prevent duplicate message handling after window reloads.

```typescript
// src/main/ipc/pty.ts
export function registerPtyHandlers(mainWindow: BrowserWindow, runtime: OrcaRuntimeService) {
  // Critical: Remove old listeners to prevent duplicates
  ipcMain.removeAllListeners('pty:write');
  
  ipcMain.handle('pty:spawn', async (event, args) => {
    // Debugging hook: log incoming spawn requests
    console.log('[debug] PTY spawn:', args);
    // ... spawn logic
  });
  
  ipcMain.on('pty:write', (event, { id, data }) => {
    // Debugging hook: inspect write traffic
    console.log(`[debug] PTY write ${id}: ${data.length} bytes`);
  });
}

```

To verify cleanup effectiveness, log `ipcMain.listenerCount('pty:write')` before and after the `removeAllListeners` call. If the count exceeds 1 after registration, you have a leak.

### Batching and Data Flow Inspection

PTY output is collected in a `pendingData` map and flushed every 8ms via `schedulePendingDataFlush` and `flushPendingData`. To inspect this batching:

1. Add `console.log('[debug] Pending data for', id, pendingData.get(id))` inside `flushPendingData`
2. Observe the timing by logging `Date.now()` in `schedulePendingDataFlush`

This reveals whether lag stems from the batching window or the underlying PTY provider.

### Environment Variable Injection

The `buildPtyHostEnv` function in [`src/main/ipc/pty.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.ts) (around lines 70-100) injects attribution variables for OpenCode, Pi, Claude, Codex, and GitHub. To verify environment construction:

```typescript
// Temporary test script
import { buildPtyHostEnv } from './src/main/ipc/pty.ts';

const env = buildPtyHostEnv('test-id', process.env, { isDaemon: false });
console.log('Injected env:', env);

```

Compare this output against the actual environment seen by the spawned shell to isolate injection failures.

## Practical Debugging Techniques

Apply these specific techniques to trace messages end-to-end:

- **Inspect raw IPC traffic**: In the renderer DevTools, invoke `await window.api.getIdentity()` and wrap it in `console.time('ipc')` / `console.timeEnd('ipc')` to measure latency.
- **Log main-process handling**: Set `process.env.NODE_DEBUG=electron-ipc` before launching Orca to see Electron's internal IPC debug output.
- **Verify handler removal on reload**: After pressing Ctrl+R in the window, check `require('electron').ipcMain.listenerCount('pty:write')` in the main process console to confirm it equals 1.
- **Force breakpoints**: Insert `debugger;` inside `registerPtyHandlers` or the PTY provider's `spawn` method, then attach the main process inspector.
- **Simulate failures**: Mock the provider's `spawn` to throw in [`src/main/ipc/pty.test.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.test.ts) and observe the `normalizeNodePtySpawnError` wrapping logic.

### Example: Debug Logging for PTY Spawn

Add this temporary instrumentation to [`src/main/ipc/pty.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.ts):

```typescript
ipcMain.handle('pty:spawn', async (event, args) => {
  console.log('[debug] PTY spawn request', {
    cols: args.cols,
    rows: args.rows,
    cwd: args.cwd,
    connectionId: args.connectionId,
  });
  // existing logic...
});

```

### Example: Logging All PTY Data in Renderer

Temporarily expose a debug channel in [`src/preload/index.ts`](https://github.com/stablyai/orca/blob/main/src/preload/index.ts):

```typescript
contextBridge.exposeInMainWorld('debug', {
  ptyData: (listener) => {
    ipcRenderer.on('pty:data', listener);
    return () => ipcRenderer.removeListener('pty:data', listener);
  },
});

```

Then subscribe in the renderer:

```typescript
window.debug.ptyData((_event, { id, data }) => {
  console.debug(`[debug] PTY ${id}: ${data}`);
});

```

## Summary

- **Orca's IPC** flows through three layers: the preload bridge ([`src/preload/index.ts`](https://github.com/stablyai/orca/blob/main/src/preload/index.ts)), renderer error handlers ([`src/renderer/src/lib/ipc-error.ts`](https://github.com/stablyai/orca/blob/main/src/renderer/src/lib/ipc-error.ts)), and main-process registrations ([`src/main/ipc/register-core-handlers.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/register-core-handlers.ts)).
- **Always verify listener cleanup** in [`src/main/ipc/pty.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.ts) using `ipcMain.removeAllListeners` to prevent duplicate message handling after reloads.
- **Inspect PTY batching** by logging the `pendingData` Map in `schedulePendingDataFlush` to diagnose latency issues.
- **Test environment injection** by calling `buildPtyHostEnv` directly to verify that OpenCode, Claude, and other attribution variables are present.
- **Use the `window.api` object** in renderer DevTools to manually trigger IPC calls and inspect responses without UI interaction.

## Frequently Asked Questions

### How do I check if my IPC handler is registered twice in Orca?

Log `ipcMain.listenerCount('channel-name')` immediately after the registration call in [`src/main/ipc/register-core-handlers.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/register-core-handlers.ts). If the count is greater than 1 after a window reload, your cleanup logic in the handler registration function (such as `registerPtyHandlers`) is missing the `removeAllListeners` call.

### Why am I not seeing PTY output in the Orca renderer?

First verify the preload bridge exposed the data listener in [`src/preload/index.ts`](https://github.com/stablyai/orca/blob/main/src/preload/index.ts). Then check [`src/main/ipc/pty.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.ts) for the `pendingData` flush logic—if the 8ms batching timer fails to fire due to a JavaScript error in the main process, data will accumulate indefinitely without sending to the renderer.

### Where does Orca inject environment variables for spawned terminals?

Environment injection happens in the `buildPtyHostEnv` function within [`src/main/ipc/pty.ts`](https://github.com/stablyai/orca/blob/main/src/main/ipc/pty.ts) (lines 70-100). This function is called for both local and daemon-backed PTY spawns, ensuring consistent attribution variables like `OPENCODE_AGENT` and `CLAUDE_CODE_AGENT` are present regardless of the connection type.

### How can I debug IPC errors that appear generic in the UI?

Errors from `ipcRenderer.invoke` are wrapped by Electron before reaching the renderer. Import the `extractIpcErrorMessage` utility from [`src/renderer/src/lib/ipc-error.ts`](https://github.com/stablyai/orca/blob/main/src/renderer/src/lib/ipc-error.ts) and modify it to return `err.stack` instead of `err.message` during development, or add `console.error(err)` in the catch block before the normalization occurs to see the full main-process stack trace.