# Troubleshooting

> Dev instance port conflicts, PATH/node discovery in packaged apps, CDP timeouts, MCP harness sync errors, SSH bridge recovery, production main.log location, and security reporting.

- 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

- `scripts/dev.sh`
- `src/main/index.ts`
- `.notes/cdp-navigation-timeout-investigation.md`
- `scripts/verify-ssh-detached-bridge-resume.js`
- `SECURITY.md`
- `TEST_RESULTS_v0043.md`

---

---
title: "Troubleshooting"
description: "Dev instance port conflicts, PATH/node discovery in packaged apps, CDP timeouts, MCP harness sync errors, SSH bridge recovery, production main.log location, and security reporting."
---

Build surfaces operational failures through main-process logging (`main.log` in production), dev-only instance naming, and console prefixes such as `[CDP Proxy]`, `[MCP IPC]`, and `[SSH Service]`. Use this page to map symptoms to ports, `userData` paths, recovery APIs, and verification commands grounded in the current codebase.

## Symptom map

| Symptom | Likely layer | First checks |
| --- | --- | --- |
| Dev app never opens / instant exit | Single-instance lock + orphaned Electron | `./scripts/dev.sh` cleanup; only one `node_modules/electron` dev instance |
| Webpack / logger port bind failure | Dev vs production ports | `DEV_WEBPACK_PORT`, `DEV_WEBPACK_LOGGER_PORT`, `lsof` |
| `Claude Code executable not found` (packaged) | PATH + Node resolution | `main.log`; `CLAUDE_CODE_NODE_PATH`; launch from Terminal vs Finder |
| Stagehand / agent browse: `Navigation timed out after 30 seconds` | CDP proxy + Page domain | `[CDP Proxy]` logs; compare with in-app browser preview |
| MCP missing in Cursor/Gemini/Codex/OpenCode | Harness config sync | `[MCP Service]` / `[MCP IPC]` errors; `~/.cursor/mcp.json` etc. |
| SSH chat forks or queue bypass after reconnect | Detached bridge + streaming state | `hasActiveRemoteProcess`; `resumeRemoteTurn`; bridge job logs on remote |
| Need production diagnostics | `main.log` under `userData` | `~/Library/Application Support/Build/main.log` (macOS) |

```text
  Dev (./scripts/dev.sh)                Production (packaged Build.app)
  ------------------------              -------------------------------
  userData: /tmp/grep-build-dev         userData: ~/Library/Application Support/Build
  main.log: disabled (DEV_INSTANCE)    main.log: append-only console tee
  Webpack: DEV_WEBPACK_PORT (9001)      Logger: port 9000 (forge default)
  CDP: 9223 (default)                   CDP: 9222 (default)
  CDP proxy: 9223 (+ auto bump)         CDP proxy: 9223 (+ auto bump)
```

## Dev instance and port conflicts

Always start development with `./scripts/dev.sh`, not `npm run start` directly. The script assigns a random `DEV_INSTANCE_NAME` (for example `bouncy-penguin`), runs `npm run setup-qmd`, kills orphaned dev Electron processes, frees the dev webpack port, and isolates user data.

<Steps>
<Step title="Confirm the dev instance name">
After `./scripts/dev.sh` starts, note the banner line `DEV INSTANCE: <adjective>-<noun>`. The status bar uses this name to distinguish dev builds.
</Step>
<Step title="Resolve silent launch failures">
`scripts/dev.sh` kills processes matching `node_modules/electron/dist/Electron.app` and `electron-forge` before start. Orphaned dev instances hold the single-instance lock and can block a new launch without an obvious error.
</Step>
<Step title="Free the dev webpack port">
Dev sets `DEV_WEBPACK_PORT=9001` and runs `lsof -ti:$DEV_WEBPACK_PORT | xargs kill -9`. Production’s electron-forge logger defaults to port `9000` via `DEV_WEBPACK_LOGGER_PORT` in `forge.config.ts`, so dev deliberately avoids colliding with a running production app on the logger port.
</Step>
<Step title="Keep production user data separate">
`GREP_DEV_USER_DATA=/tmp/grep-build-dev` is applied via `app.setPath('userData', …)` in `src/main/index.ts`. Settings are copied from production once (never symlinked) so dev edits do not corrupt production `claudette-settings.json`.
</Step>
</Steps>

<Warning>
Do not run multiple dev Electron instances for the same worktree. `app.requestSingleInstanceLock()` exits secondary launches unless `GREP_DISABLE_SINGLE_INSTANCE=1`.
</Warning>

| Variable | Dev typical value | Role |
| --- | --- | --- |
| `DEV_INSTANCE_NAME` | `fuzzy-penguin` (random) | Enables dev CDP port `9223`; disables `main.log` tee |
| `GREP_DEV_USER_DATA` | `/tmp/grep-build-dev` | Isolated `electron-store` files |
| `DEV_WEBPACK_PORT` | `9001` | Webpack dev server (`forge.config.ts`) |
| `DEV_WEBPACK_LOGGER_PORT` | `9000` (default) | Forge logger port when unset |
| `GREP_DISABLE_SINGLE_INSTANCE` | unset | Set to `1` only when debugging multi-instance behavior |

## Production `main.log` location

When `DEV_INSTANCE_NAME` is unset (production / packaged runs), `src/main/index.ts` tees `console.log`, `console.warn`, and `console.error` into an append-only file:

```text
<userData>/main.log
```

On macOS with `productName: "Build"`, the default path is:

```text
~/Library/Application Support/Build/main.log
```

Legacy installs may still have data under `G-Build` or `Grep Build`; first launch migrates copies into `Build` when `.migrated-from-grep-build` is absent.

<Note>
Dev runs skip file logging entirely because `DEV_INSTANCE_NAME` is set. Use the terminal where `./scripts/dev.sh` is running, or open DevTools when `GREP_DEV_USER_DATA` is active (`mainWindow.webContents.openDevTools()`).
</Note>

## PATH and Node discovery (packaged macOS)

GUI-launched macOS apps do not inherit a login-shell `PATH`. Build applies two layers at startup in `src/main/index.ts`:

1. **`fix-path`** — restores shell-like PATH for the Electron main process.
2. **Explicit fallbacks** — prepends `/usr/local/bin`, `/opt/homebrew/bin`, `/opt/local/bin`, and common version-manager shim directories when missing.

Local Claude Code spawns (non-SSH) resolve Node through `ClaudeService.resolveLocalNodeExecutable()`:

- `CLAUDE_CODE_NODE_PATH`, `NODE`, `npm_node_execpath`
- Every entry on `process.env.PATH`
- Homebrew, nvm (newest version dir first), nodenv, asdf, volta paths

`createLocalClaudeCodeProcess` rewrites the SDK spawn when a Node binary is found (`node-override` / `node-script` modes). If resolution fails, logs show `Could not resolve a local Node executable explicitly; falling back to PATH lookup`.

<Tip>
For packaged apps launched from Finder, set `CLAUDE_CODE_NODE_PATH` to an absolute Node binary (for example `/opt/homebrew/bin/node`) in the environment before launch, or open Build from a Terminal where `node` is already on PATH.
</Tip>

`src/main/utils/local-executable.ts` additionally skips macOS-quarantined binaries when scanning PATH (relevant for user-installed CLIs).

Release notes document a related fix: misleading “Claude Code executable not found” when packaged builds could not find `node` on PATH; local spawns now route JavaScript entrypoints through an explicit Node candidate when resolved.

## CDP ports and navigation timeouts

Build exposes Chrome DevTools Protocol on two related surfaces:

| Surface | Default port | Override |
| --- | --- | --- |
| Electron remote debugging | `9222` production; `9223` when `DEV_INSTANCE_NAME` or `NODE_ENV=development` | `ELECTRON_CDP_PORT` |
| In-app CDP WebSocket proxy (`cdpProxyService`) | `9223` | `CDP_PROXY_PORT`; auto-increments on `EADDRINUSE` |

Stagehand connects Playwright over CDP to the proxy, which forwards debugger events from session webviews. `stagehand.service.ts` races `page.goto(url, { waitUntil: 'domcontentloaded' })` against a **30 second** timer and throws `Navigation timed out after 30 seconds` when the race loses.

### Known failure mode: Page domain not enabled on attach

Internal investigation (`.notes/cdp-navigation-timeout-investigation.md`) documents the root cause for hung Playwright navigation:

- After `Target.attachToTarget`, the proxy attaches `wc.debugger` and forwards messages.
- **`Page.enable` is not sent** in `cdp-proxy.service.ts` at attach time (verified: no `Page.enable` in that file).
- Electron suppresses `Page.domContentEventFired` / `Page.loadEventFired` until the Page domain is enabled, so Stagehand waits until the 30s timeout even when the webview visibly loaded.

By contrast, `browser.service.ts` in-app navigation calls `Page.navigate` then `Page.enable` (and falls back to `loadURL` on error)—preview navigation can succeed while Stagehand times out.

<Warning>
If agent-driven browsing times out but manual preview navigation works, treat it as a CDP proxy lifecycle issue, not a network block. Watch for `[CDP Proxy] Event[session-N] → Playwright: Page.*` lines after attach.
</Warning>

`cdp-proxy.service.ts` also enforces a **30s** default per CDP command (`COMMAND_TIMEOUT_MS`), emitting errors like `CDP command '…' timed out after 30000ms (debugger may be detached or target destroyed)` when the debugger detaches mid-command.

## MCP harness sync errors

Installed MCP servers live in `claudette-mcp-servers` (`electron-store`). Build mirrors selected entries into per-harness config files on disk:

| Harness | Target file |
| --- | --- |
| Cursor | `~/.cursor/mcp.json` |
| Gemini | `~/.gemini/settings.json` |
| Codex | `~/.codex/config.toml` |
| OpenCode | OpenCode config path from `mcp.service.ts` |

`mcpService.syncLocalHarnessConfigs()` returns `{ cursor?, gemini?, codex?, opencode?, errors: Record<string, string> }`. Any harness that throws records `result.errors.<harness>` with the exception message while other harnesses may still show `synced`.

Startup (`app.on('ready')`) calls `syncLocalHarnessConfigs()`; failures log `[Main] Failed to sync local MCP harness configs on startup`. The MCP IPC layer re-syncs after install/uninstall and queues SSH remote sync.

### SSH remote MCP sync

For connected SSH sessions, `mcp.ipc.ts`:

- Wraps each `sshService.syncMcpConfigsToRemote` in a **30s** timeout (`REMOTE_MCP_SYNC_TIMEOUT_MS`).
- Logs `[MCP IPC] Error syncing to SSH session:` on failure.
- Serializes overlapping syncs via `remoteMcpSyncInFlight` / `remoteMcpSyncQueued`.

Remote sync depends on **stdio-to-HTTP bridges** for native stdio MCP servers. Warnings such as `Could not start MCP stdio bridges` or `Could not sync harness MCP configs to remote` usually mean bridge startup failed before config upload. SSH harness sync is cached per host for **5 minutes** (`HARNESS_MCP_CACHE_TTL_MS`).

<Steps>
<Step title="Inspect local harness errors">
Trigger a sync from Extensions/MCP UI or restart the app; search `main.log` or the dev terminal for `[MCP Service] Local harness MCP sync complete` and non-empty `errors`.
</Step>
<Step title="Validate on-disk configs">
Confirm merged entries appear under the harness paths in the table above. Codex uses TOML merge logic; Cursor/Gemini use JSON merge.
</Step>
<Step title="Re-sync SSH after bridge fixes">
Reconnect the SSH session or install the MCP server again so `syncHarnessesAndSshSessions` runs with active `sshService.isConnected(session.id)`.
</Step>
</Steps>

## SSH detached bridge recovery

SSH Claude runs use a **detached bridge** (`REMOTE_DETACHED_BRIDGE_SCRIPT` uploaded to the remote) instead of fragile tmux/FIFO pipes. The bridge:

- Spawns `claude` (or configured command) with stdio captured to an append-only `stdout.log`
- Exposes a Unix socket for stdin
- Survives app quit, sleep, and transient SSH drops (`app.on('will-quit')` explicitly does **not** kill remote jobs)

### Recovery APIs

| API | Behavior |
| --- | --- |
| `ssh.hasActiveRemoteProcess(sessionId)` | True if a non-recovered detached bridge job is active (`command` empty or `claude`) or legacy tmux session exists |
| `claude.resumeRemoteTurn(sessionId, model?)` | Attaches latest recoverable bridge job and streams recovered stdout; errors if no job or stream already active |
| `sshService.getLatestRecoverableRemoteProcess` | Picks active jobs, or completed jobs within **24h** with metadata and log bytes |

Renderer `session.store.ts` starts `startRemoteProcessMonitor` when switching to an SSH session that is not locally marked streaming. If the backend has no active query, it calls `resumeRemoteTurn` and reloads the transcript.

Recoverable jobs must not have `recovered.json`. Completed jobs need `metadata.json` and non-zero log size.

### Bridge startup timeout

Spawning the bridge waits until `stdin.sock` and `stdout.log` exist (10s deadline). Failure throws `Timed out waiting for detached remote process bridge`.

### Verification script

```bash
node scripts/verify-ssh-detached-bridge-resume.js
```

This simulates mid-run disconnect and asserts recovered text `hello after disconnect` plus a `result` line in the bridge log (same JSONL shape Claude SDK uses).

### Queue / streaming after laptop sleep

Historical SSH queue bypass (documented in `TEST_RESULTS_v0043.md`) occurred when `isStreaming` stayed false after reconnect while remote Claude kept running. Mitigations in tree:

- Detached bridge jobs instead of blocking FIFOs
- `hasActiveRemoteProcess` driving remote process monitors
- Message queue injection via `streamInput` after tool completion

Good console signals: `[SessionStore] Reattaching to detached SSH turn`, `Session is streaming, queueing message`. Bad signal: `Session is NOT streaming, sending as new query` during an active remote turn.

## Security reporting

Per `SECURITY.md`:

<Steps>
<Step title="Do not file public GitHub issues for vulnerabilities">
Email **security@parcha.ai** instead.
</Step>
<Step title="Include reproduction detail">
Provide description, reproduction steps, impact, and optional fix suggestion.
</Step>
<Step title="Expect response targets">
Acknowledgement within **48 hours**; fix or mitigation plan within **7 days** for in-scope issues.
</Step>
</Steps>

**Scope:** Build application source and its build/distribution pipeline. npm dependencies are out of direct policy scope but reports are still welcome.

**API keys:** Stored locally via `electron-store`; transmitted only to configured providers (Anthropic, OpenAI, ElevenLabs, etc.), not to Build-operated analytics servers by default.

## Environment variables reference

<ParamField body="CLAUDE_CODE_NODE_PATH" type="string">
Absolute path to Node for local Claude Code spawns when PATH discovery fails in packaged apps.
</ParamField>

<ParamField body="ELECTRON_CDP_PORT" type="string">
Overrides Electron `remote-debugging-port` (default `9222` production, `9223` dev).
</ParamField>

<ParamField body="CDP_PROXY_PORT" type="number">
CDP WebSocket proxy listen port (default `9223`, auto-increment if busy).
</ParamField>

<ParamField body="GREP_DEV_USER_DATA" type="path">
Set by `scripts/dev.sh` to `/tmp/grep-build-dev` for isolated dev persistence.
</ParamField>

<ParamField body="GREP_DISABLE_SINGLE_INSTANCE" type="string">
Set to `1` to bypass `requestSingleInstanceLock` (debug only).
</ParamField>

<ParamField body="DEV_WEBPACK_PORT" type="number">
Webpack dev server port (dev script sets `9001`).
</ParamField>

## Related pages

<CardGroup>
<Card title="Electron architecture" href="/electron-architecture">
Main vs renderer, `userData`, CDP ports, and dev isolation.
</Card>
<Card title="npm scripts reference" href="/npm-scripts-reference">
When to use `./scripts/dev.sh` vs `npm run start`, verify scripts.
</Card>
<Card title="SSH remote sessions" href="/ssh-remote-sessions">
SSHConfig, resume candidates, teleport, and bridge behavior.
</Card>
<Card title="MCP servers" href="/mcp-servers">
Install flow, harness sync targets, and SDK loading.
</Card>
<Card title="Browser preview" href="/browser-preview">
Webview CDP, inspector, and Stagehand integration.
</Card>
<Card title="Build and release" href="/build-and-release">
Packaging paths, versioning, and production artifacts.
</Card>
</CardGroup>
