# How to Debug OpenWork Electron Issues Using Chrome DevTools Protocol (CDP)

> Debug OpenWork Electron issues using Chrome DevTools Protocol (CDP). Connect DevTools to your OpenWork app at http://127.0.0.1:9825 to inspect and troubleshoot the renderer process.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-01

---

**OpenWork exposes a CDP endpoint at `http://127.0.0.1:9825` by default, allowing you to connect Chrome DevTools or programmatic clients to inspect, interact with, and troubleshoot the Electron renderer process.**

The OpenWork desktop application runs on Electron with built-in **Chrome DevTools Protocol (CDP)** support for development and debugging. Understanding how this CDP endpoint is created, how to connect to it, and how to use the helper utilities in the codebase enables efficient troubleshooting of startup failures, port conflicts, and UI behavior issues. This guide walks through the complete CDP debugging workflow as implemented in the `different-ai/openwork` repository.

---

## How OpenWork Starts the CDP Endpoint

The CDP endpoint initialization flows through several key files:

### Electron Main Process Launch

In `apps/desktop/electron/main.mjs` (around line 30), the Electron main process reads `ELECTRON_EXTRA_LAUNCH_ARGS` and starts Vite with `--remote-debugging-port`:

```javascript
// apps/desktop/electron/main.mjs
// Launches Electron with CDP debugging enabled
const launchArgs = {
  // CDP port passed via ELECTRON_EXTRA_LAUNCH_ARGS
  remoteDebuggingPort: process.env.ELECTRON_EXTRA_LAUNCH_ARGS?.match(/--remote-debugging-port=(\d+)/)?.[1]
};

```

### Surface Environment Configuration

In [`dev/evals/packages/hosts/src/local.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/packages/hosts/src/local.ts) (around line 350), the `electronSurfaceEnv` function generates an isolated profile folder and sets the environment variables:

```typescript
// dev/evals/packages/hosts/src/local.ts
export function electronSurfaceEnv(opts: { cdpPort: number }) {
  const profileDir = mkdtempSync(path.join(tmpdir(), 'openwork-electron-'));
  
  return {
    ELECTRON_EXTRA_LAUNCH_ARGS: `--remote-debugging-port=${opts.cdpPort} --user-data-dir=${profileDir}`,
    OPENWORK_EVAL_CDP_URL: `http://127.0.0.1:${opts.cdpPort}`,
    OPENWORK_ELECTRON_LOG_PATH: path.join(profileDir, 'electron.log')
  };
}

```

### Surface Handle Creation

In [`dev/evals/runner/surfaces.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/surfaces.ts) (around line 62), the `electron()` function creates a surface handle containing the CDP URL:

```typescript
// dev/evals/runner/surfaces.ts
export async function electron(name: string, opts: ElectronSurfaceOpts): Promise<SurfaceHandle> {
  const cdpPort = await getFreePort();
  const env = electronSurfaceEnv({ cdpPort });
  
  return {
    kind: 'electron',
    name,
    cdpUrl: env.OPENWORK_EVAL_CDP_URL,  // Stored for debugging access
    meta: { log: env.OPENWORK_ELECTRON_LOG_PATH }
  };
}

```

### CLI Output

When you run `pnpm dev` or `pnpm dev:worktree`, the CLI in [`dev/evals/runner/owt-cli.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/owt-cli.ts) (around line 380) prints the CDP URL:

```

[openwork] electron main CDP: http://127.0.0.1:9825

```

---

## Connecting Chrome DevTools to the Electron CDP Endpoint

### Method 1: Chrome Inspect Page

1. Open Chrome and navigate to `chrome://inspect/#devices`
2. Click **Configure...** and add `127.0.0.1:9825`
3. Under "Remote Target," find your Electron window
4. Click **inspect** to open DevTools attached to the renderer process

### Method 2: Direct DevTools URL

Navigate directly to:

```

chrome-devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9825/devtools/page/1

```

This provides full access to Elements, Console, Network, Application, and other DevTools panels.

### Method 3: Programmatic Connection

Use the built-in CDP client from [`dev/evals/packages/cdp/src/cdp.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/packages/cdp/src/cdp.ts):

```typescript
import { waitForCdp, connect } from '@/dev/evals/packages/cdp/src/cdp';

const CDP_URL = 'http://127.0.0.1:9825';

// Verify endpoint is reachable
await waitForCdp(CDP_URL);

// Connect and inspect the page
const client = await connect(CDP_URL);
const { result } = await client.send('Runtime.evaluate', {
  expression: 'document.title'
});
console.log(result.value);  // Prints the page title

```

---

## Using OpenWork's CDP Helper Library

The codebase provides high-level helpers in [`dev/evals/runner/context.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/context.ts) for common debugging operations:

### Available Helpers

| Helper | Purpose | Location |
|--------|---------|----------|
| `browser_snapshot` | Captures accessibility tree and layout metrics | [`context.ts`](https://github.com/different-ai/openwork/blob/main/context.ts) |
| `browser_click` | Sends synthetic click to element by UID | [`context.ts`](https://github.com/different-ai/openwork/blob/main/context.ts) |
| `browser_fill` | Types into focused input elements | [`context.ts`](https://github.com/different-ai/openwork/blob/main/context.ts) |
| `browser_eval` | Executes arbitrary JavaScript in renderer | [`context.ts`](https://github.com/different-ai/openwork/blob/main/context.ts) |

### Example: Taking a Snapshot and Clicking

```typescript
import { browser_snapshot, browser_click } from '@/dev/evals/runner/context';

const CDP_URL = process.env.OPENWORK_EVAL_CDP_URL ?? 'http://127.0.0.1:9825';

// Capture current UI state
const snapshot = await browser_snapshot({ browser_url: CDP_URL });
console.log(`Found ${snapshot.nodes.length} accessibility nodes`);

// Find and click a button
const button = snapshot.nodes.find(n => n.role?.name === 'button');
if (button) {
  await browser_click({ 
    browser_url: CDP_URL, 
    uid: button.backendDOMNodeId 
  });
}

```

**Note:** All helpers throw descriptive errors if the CDP client cannot be obtained (see [`context.ts`](https://github.com/different-ai/openwork/blob/main/context.ts) around line 195).

---

## Common Debugging Scenarios

### "CDP Endpoint Not Reachable"

**Cause:** Electron failed to start or port is in use.

**Fix:** Check the console banner for the actual CDP URL. Override with a free port:

```bash
export OPENWORK_EVAL_CDP_URL=http://127.0.0.1:9830
pnpm dev

```

### Electron Logs Not Created

**Cause:** `electronSurfaceEnv` was not applied—profile isolation missing.

**Fix:** Always launch via `pnpm dev` or `pnpm dev:worktree`. The log path is stored in `handle.meta.log` from the surface handle.

### CDP Command Timeouts

** Cause:** Default timeout of 20 seconds (`DEFAULT_CDP_TIMEOUT_MS`) exceeded.

**Fix:** Increase timeout in connection options:

```typescript
import { connect } from '@/dev/evals/packages/cdp/src/cdp';

const client = await connect(CDP_URL, {
  connectTimeoutMs: 60000,
  sendTimeoutMs: 60000
});

```

### Port Collision on Daytona

**Cause:** Daytona reserves ports 9825–9829 for Electron surfaces.

**Fix:** Use `OPENWORK_EVAL_CDP_URL` to point to a non-reserved port above 9829.

### "Targeting a Specific CDP Target Requires cdpBaseUrl"

**Cause:** Helper called without base URL for page-specific operations.

**Fix:** Pass the base URL from the surface handle:

```typescript
await browser_click({ 
  browser_url: handle.cdpUrl,  // Required parameter
  uid: targetId 
});

```

---

## Complete Debugging Script

Save as [`scripts/debug-electron-cdp.ts`](https://github.com/different-ai/openwork/blob/main/scripts/debug-electron-cdp.ts) and run with `pnpm ts-node scripts/debug-electron-cdp.ts`:

```typescript
import { waitForCdp, connect } from '@/dev/evals/packages/cdp/src/cdp';
import { browser_snapshot, browser_click, browser_eval } from '@/dev/evals/runner/context';

const CDP_URL = process.env.OPENWORK_EVAL_CDP_URL?.trim() ?? 'http://127.0.0.1:9825';

async function main() {
  // 1. Verify CDP endpoint
  await waitForCdp(CDP_URL);
  console.log('✅ CDP endpoint reachable');

  // 2. Evaluate JavaScript directly
  const client = await connect(CDP_URL);
  const titleResult = await client.send('Runtime.evaluate', {
    expression: 'document.title'
  });
  console.log('📄 Page title:', titleResult.result.value);

  // 3. Use high-level helpers
  const snapshot = await browser_snapshot({ browser_url: CDP_URL });
  console.log(`🪞 Snapshot: ${snapshot.nodes.length} nodes`);

  // 4. Execute custom evaluation
  const customResult = await browser_eval({
    browser_url: CDP_URL,
    expression: 'window.location.href'
  });
  console.log('🔗 Current URL:', customResult.result.value);

  // 5. Interactive element detection
  const clickable = snapshot.nodes.find(n => 
    n.role?.name === 'button' || n.role?.name === 'link'
  );
  
  if (clickable) {
    console.log(`🖱 Clicking ${clickable.role.name}: ${clickable.name?.value}`);
    await browser_click({ 
      browser_url: CDP_URL, 
      uid: clickable.backendDOMNodeId 
    });
  } else {
    console.log('ℹ️ No clickable elements found');
  }
}

main().catch(err => {
  console.error('❌ Debug failed:', err);
  process.exit(1);
});

```

---

## Key Source Files Reference

| File | Purpose |
|------|---------|
| `apps/desktop/electron/main.mjs` | Electron entry point with CDP port configuration |
| [`dev/evals/packages/hosts/src/local.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/packages/hosts/src/local.ts) | Profile isolation and environment setup (`electronSurfaceEnv`) |
| [`dev/evals/runner/surfaces.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/surfaces.ts) | Surface factory creating handles with CDP URLs |
| [`dev/evals/runner/context.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/context.ts) | High-level CDP helpers (`browser_snapshot`, `browser_click`, etc.) |
| [`dev/evals/packages/cdp/src/cdp.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/packages/cdp/src/cdp.ts) | Low-level CDP connection, target discovery, and timeout handling |
| [`dev/evals/runner/owt-cli.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/owt-cli.ts) | CLI that outputs CDP URLs and log paths |

---

## Summary

- **OpenWork automatically exposes a CDP endpoint** at `http://127.0.0.1:9825` during development, controlled by `ELECTRON_EXTRA_LAUNCH_ARGS` in `apps/desktop/electron/main.mjs`
- **Connect via Chrome DevTools** using `chrome://inspect` or programmatically using [`dev/evals/packages/cdp/src/cdp.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/packages/cdp/src/cdp.ts)
- **Use built-in helpers** in [`dev/evals/runner/context.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/context.ts) for snapshots, clicks, fills, and JavaScript evaluation
- **Override the default port** with `OPENWORK_EVAL_CDP_URL` when facing conflicts
- **Check surface handles** for log paths and CDP URLs when troubleshooting startup issues

---

## Frequently Asked Questions

### How do I find the current CDP URL for my OpenWork Electron instance?

Check the console output when running `pnpm dev`—the CLI prints `electron <name> CDP: <url>`. Alternatively, inspect the surface handle returned by `electron()` in [`dev/evals/runner/surfaces.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/surfaces.ts), which stores the URL in `handle.cdpUrl`.

### Can I use the CDP helpers outside the OpenWork codebase?

Yes, but you need to replicate the environment setup. The helpers depend on `OPENWORK_EVAL_CDP_URL` being set. Import the [`cdp.ts`](https://github.com/different-ai/openwork/blob/main/cdp.ts) module and call `connect()` with any valid Chrome DevTools Protocol endpoint, not just OpenWork's Electron instance.

### Why does my CDP connection timeout after 20 seconds?

The default timeout is defined in [`dev/evals/packages/cdp/src/cdp.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/packages/cdp/src/cdp.ts) as `DEFAULT_CDP_TIMEOUT_MS` (20000ms). Pass custom `connectTimeoutMs` or `sendTimeoutMs` values to `connect()` to extend this for slow operations or heavy pages.

### How do I debug multiple Electron windows simultaneously?

Each surface created via `electron()` in [`dev/evals/runner/surfaces.ts`](https://github.com/different-ai/openwork/blob/main/dev/evals/runner/surfaces.ts) receives a unique port via `getFreePort()`. Store each handle's `cdpUrl` separately and connect independent CDP clients to each endpoint. The `listTargets()` function in [`cdp.ts`](https://github.com/different-ai/openwork/blob/main/cdp.ts) can enumerate pages within a single endpoint.