# Electron process model

> Main vs renderer boundaries, service layout under src/main/services, webpack packaging, custom protocols (monaco-asset), CDP ports, and dev vs production userData paths.

- Repository: Parcha-ai/build
- GitHub: https://github.com/Parcha-ai/build
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b/llms-full.txt

## Source Files

- `src/main/index.ts`
- `src/main/preload.ts`
- `webpack.main.config.ts`
- `webpack.renderer.config.ts`
- `forge.config.ts`
- `src/renderer/monaco-config.ts`

---

---
title: "Electron process model"
description: "Main vs renderer boundaries, service layout under src/main/services, webpack packaging, custom protocols (monaco-asset), CDP ports, and dev vs production userData paths."
---

Build is an Electron Forge application: a Node.js **main process** owns system integration, IPC, and agent services; a React **renderer** runs the IDE UI behind `contextBridge`; optional **webview** renderers host browser preview. Webpack bundles three entry surfaces (main, preload, renderer), and packaged builds externalize native modules into `Resources/node_modules`.

## Process boundaries

| Process | Entry | Node integration | Primary role |
|---------|-------|------------------|--------------|
| Main | `src/main/index.ts` → `.webpack/main` | Full Node.js | Window lifecycle, IPC, services, protocols, CDP proxy |
| Preload | `src/main/preload.ts` (webpack preload entry) | Limited (`ipcRenderer`) | Typed `window.electronAPI` via `contextBridge` |
| Renderer | `src/renderer/index.tsx` | Disabled (`nodeIntegration: false`, `contextIsolation: true`) | React UI, Zustand stores, Monaco |
| Webview | Created by renderer `<webview>` | Disabled; `sandbox: false` | In-app browser preview (`persist:browser` partition) |

```mermaid
flowchart TB
  subgraph main["Main process (index.ts)"]
    IPC["ipc/*.ts handlers"]
    SVC["services/*.ts"]
    CDP["cdp-proxy.service"]
  end
  subgraph bridge["Preload (preload.ts)"]
    API["contextBridge → electronAPI"]
  end
  subgraph ui["Renderer (index.tsx)"]
    React["React + Zustand"]
    Monaco["monaco-config.ts"]
  end
  subgraph preview["Webview renderers"]
    WV["persist:browser partition"]
  end
  React -->|invoke / on| API
  API -->|IPC| IPC
  IPC --> SVC
  SVC --> CDP
  React --> WV
  WV -->|register-webview IPC| SVC
  CDP -->|debugger API| WV
```

Main-window `webPreferences` set `sandbox: false` (required for `node-pty`), `webviewTag: true`, and load the webpack-generated preload script. The renderer imports `./monaco-config` before React so Monaco paths resolve to `monaco-asset://` in Electron.

## Main process layout

### Bootstrap (`src/main/index.ts`)

Startup runs before `app.ready`:

- **Dev instance name** — `DEV_INSTANCE_NAME` from `./scripts/dev.sh` (shown in UI).
- **Production logging** — When not in dev, `console.*` is tee’d to `{userData}/main.log`.
- **Dev userData override** — `GREP_DEV_USER_DATA` calls `app.setPath('userData', …)` so dev never writes production settings.
- **Chromium remote debugging** — `remote-debugging-port` from `ELECTRON_CDP_PORT`, or `9223` in development / named dev instance, else `9222`.
- **PATH fix** — `fix-path` plus Homebrew/nvm fallbacks so packaged Finder launches can spawn `node`.
- **Privileged scheme** — `monaco-asset` registered via `protocol.registerSchemesAsPrivileged` before ready.
- **Single instance** — Unless `GREP_DISABLE_SINGLE_INSTANCE=1`, a second launch focuses the existing window.

On `app.ready`: migrate legacy app-support folders, register all IPC handlers, start `powerService`, sync MCP harness configs, `createWindow()`, and `cdpProxyService.start()`.

### IPC layer (`src/main/ipc/`)

Handlers are thin adapters over services. Domains include auth, session, git, terminal, claude/codex, settings, dev, fs, audio/realtime/voice, extension, browser, ssh, memory, secure-keys, qmd, mcp, plugin, openclaw, analytics, and queue. Registration is centralized in `registerIPCHandlers()` inside `index.ts`.

### Services (`src/main/services/`)

Business logic lives in services; IPC files delegate to them. Grouped by concern:

| Domain | Representative modules |
|--------|-------------------------|
| Agent harnesses | `claude.service`, `codex.service`, `cursor.service`, `cursor-cli.service`, `gemini.service`, `opencode.service` |
| Auto Build routing | `auto-router.service`, `flue-meta-router.service`, `harness-policy.service`, `harness-capabilities.ts` |
| Sessions & workspaces | `session.service`, `docker.service`, `ssh.service`, `transcript.service`, `message-queue.service` |
| Browser & CDP | `browser.service`, `cdp-proxy.service`, `stagehand.service`, `computer-use.service`, `renderer-cdp.service` |
| Git & terminal | `git.service`, `terminal.service` |
| Settings & memory | `settings.service`, `memory.service`, `qmd.service`, `secure-keys.service` |
| MCP & extensions | `mcp.service`, `mcp-stdio-bridge.service`, `extension.service`, `plugin.service`, `gstack.service` |
| Voice & audio | `audio.service`, `realtime.service`, `elevenlabs-voice.service` |
| Auth & analytics | `auth.service`, `analytics.service`, `posthog.service` |
| Utilities | `auth`, `document`, `openclaw`, `power`, `wakeup` |

`browser.service` maps coding sessions to webview `webContentsId` values (via renderer `browser:register-webview`) and issues CDP commands through Electron’s debugger API. `cdp-proxy.service` exposes Playwright-compatible HTTP/WebSocket endpoints that bridge external tools (e.g. Stagehand) to those webviews.

Persistence uses `electron-store` JSON files under `userData`, with `CachedStore` debouncing large session writes. Store names include `claudette-settings`, `claudette-sessions`, `claudette-mcp-servers`, `claudette-memory`, `claudette-qmd`, and related files.

## Preload and renderer bridge

`preload.ts` builds a single `electronAPI` object (auth, sessions, terminal, git, claude, browser, settings, etc.) and exposes it with:

```typescript
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
```

The renderer types this as `window.electronAPI` in `index.tsx`. Invoke handlers use `ipcRenderer.invoke`; streaming and events use `ipcRenderer.on` with unsubscribe helpers. No Node APIs are exposed to the renderer.

<Note>
Packaged builds intercept global shortcuts in the main process (`before-input-event`) and forward them to the renderer via `IPC_CHANNELS.APP_SHORTCUT_TRIGGERED`, so keyboard commands work even when focus is inconsistent.
</Note>

## Webpack and Electron Forge packaging

### Webpack entries

| Config | Entry / outputs | Notes |
|--------|-----------------|-------|
| `webpack.main.config.ts` | `./src/main/index.ts` | Externals: `node-pty`, `@anthropic-ai/claude-agent-sdk`, `@anthropic-ai/sdk`, `@cursor/sdk`, `docx` |
| `webpack.renderer.config.ts` | Forge entry `src/renderer/index.tsx` | PostCSS/Tailwind via shared rules |
| Forge `WebpackPlugin` | Preload: `./src/main/preload.ts` | Injected globals: `MAIN_WINDOW_WEBPACK_ENTRY`, `MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY` |

Shared `webpack.plugins.ts` adds `ForkTsCheckerWebpackPlugin`, `MonacoWebpackPlugin`, and optional `DefinePlugin` keys from `.env.production`.

`package.json` sets `"main": ".webpack/main"`. Production artifacts land under `./out/v{version}/` (see `forge.config.ts`).

### Packaged native dependencies

`forge.config.ts` `postPackage` copies webpack-externalized packages into `Build.app/Contents/Resources/node_modules` (including `monaco-editor` for the custom protocol). QMD platform bundles copy to `Resources/qmd`. Fuses disable Node-in-renderer patterns and enable ASAR integrity.

Dev server ports (Forge):

| Variable | Default in Forge | Dev script override |
|----------|------------------|---------------------|
| `DEV_WEBPACK_PORT` | `3000` | `9001` (`scripts/dev.sh`) |
| `DEV_WEBPACK_LOGGER_PORT` | `9000` | unchanged |

Use `./scripts/dev.sh` for development (not bare `npm run start`): it sets `DEV_INSTANCE_NAME`, runs `setup-qmd`, kills stale dev Electron processes, assigns `/tmp/grep-build-dev` userData, and syncs settings from production only when dev files are missing.

## Custom protocols

### `monaco-asset://`

Registered as a privileged scheme before `app.ready`. After ready, `protocol.handle('monaco-asset', …)` serves files from:

- **Dev** — `{projectRoot}/node_modules/...` (resolved from `.webpack/main` up two levels).
- **Packaged** — `{process.resourcesPath}/node_modules/...` (copied at package time).

The renderer configures Monaco in `monaco-config.ts`:

```typescript
loader.config({
  paths: { vs: 'monaco-asset://app/node_modules/monaco-editor/min/vs' },
});
```

CSP headers on the default session explicitly allow `monaco-asset:` for scripts, styles, workers, and connections.

### `grep://` (OAuth)

`protocol.registerHttpProtocol('grep', …)` handles `grep://oauth/callback` and forwards the authorization code to the renderer on `auth:oauth-callback`. `auth.service` uses redirect URI `grep://oauth/callback`.

## CDP ports and debugging surfaces

Build uses **two** CDP-related listeners:

| Surface | Purpose | Default port | Override |
|---------|---------|--------------|----------|
| Chromium `remote-debugging-port` | DevTools protocol on the Electron app (main + renderer debugging) | `9223` dev / `9222` production | `ELECTRON_CDP_PORT` |
| `cdp-proxy.service` | HTTP `/json/*` + WebSocket bridge for webview targets (Playwright / Stagehand) | `9223` (increments on `EADDRINUSE`) | `CDP_PROXY_PORT` |

The CDP proxy binds **localhost only** (`127.0.0.1`). Discovery endpoints include `/json/version` and browser WebSocket `ws://localhost:{port}/devtools/browser`. Webviews register with `browser.service`; the proxy attaches via `webContents.debugger` and forwards Target-domain events to connected clients.

Optional automation: set `GREP_RENDERER_CDP_SCRIPT` to run a module via `webContents.debugger` after the main window loads (`renderer-cdp.service`).

<Tip>
If Stagehand or Playwright cannot connect, check main logs for `[CDP Proxy]` port selection and confirm the browser panel registered the webview (`browser:register-webview`).
</Tip>

## userData: development vs production

| Mode | Path | How it is set |
|------|------|----------------|
| Production (macOS) | `~/Library/Application Support/Build` | Electron default for app name **Build** |
| Legacy names | `~/Library/Application Support/G-Build`, `Grep Build` | One-time migration into **Build** on first launch |
| Development | `/tmp/grep-build-dev` | `GREP_DEV_USER_DATA` from `scripts/dev.sh` |

<Warning>
Dev settings are copied from production only when missing in `/tmp/grep-build-dev`; dev never symlinks production files (avoids `CachedStore` flush corrupting production data).
</Warning>

Common files in `userData`:

| File | Role |
|------|------|
| `claudette-settings.json` | App settings, API keys, router config |
| `claudette-sessions.json` | Session records |
| `claudette-mcp-servers.json` | MCP server definitions |
| `claudette-memory.json`, `claudette-qmd.json` | Memory and QMD preferences |
| `main.log` | Main-process console tee (production only) |
| `sessions/` | Per-session paths used by `session.service` |

Dev opens DevTools automatically when `GREP_DEV_USER_DATA` is set. Production does not.

## Webview session partition

Browser preview uses `session.fromPartition('persist:browser')` with permissive handlers for preview OAuth. Webviews use `nodeIntegration: false`, `contextIsolation: false`, `webSecurity: false`, and `sandbox: false`. Ctrl+Tab in a webview is forwarded to the main renderer for session switching.

## Multi-window behavior

`createNewWindow()` spawns additional `BrowserWindow` instances sharing the same preload and webpack entry. `getMainWindow()` prefers the focused window; `broadcastToAll()` sends IPC events to every open window (renderer filters by `sessionId`).

## Related pages

<CardGroup>
  <Card title="IPC and preload bridge" href="/ipc-bridge">
    Typed channels, invoke vs push events, and secure preload exposure.
  </Card>
  <Card title="Browser preview and inspection" href="/browser-preview">
    Webview navigation, CDP attachment, DOM inspector, and Stagehand.
  </Card>
  <Card title="Installation" href="/installation">
    Dev vs production user data separation and prerequisites.
  </Card>
  <Card title="npm scripts reference" href="/npm-scripts-reference">
    When to use ./scripts/dev.sh vs npm run start.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    CDP timeouts, port conflicts, and main.log location.
  </Card>
</CardGroup>
