[GH-ISSUE #2269] [Bug] capture_electron_scenario kills the REA process (MCP server exits) when executable_path is an existing non-executable file #750

Closed
opened 2026-10-15 18:27:57 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @BeLit-Development-2 on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2269

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

MCP or agent integration

Observed behavior

capture_electron_scenario terminates the whole REA process when executable_path names an existing file that is not a launchable Electron executable. The request never gets a typed error back.

  • MCP: the rea mcp server exits with code 1 while the call is in flight. The client sees Error: Connection closed. Everything held by that server is gone: the active native target, retained Evidence, and the residual-unknown registry, including state belonging to other concurrent callers. In Claude Code the client restarted the server automatically (new PID), so later calls appeared to work but reported earlier Evidence IDs as "not retained in this session".
  • CLI: rea capture-electron-scenario <input.json> exits 1 with an uncaught Node stack trace on stderr and no JSON envelope.

Two distinct uncaught errors from playwright-core, depending on the file:

executable_path points at Time to crash Uncaught error
a .har (JSON text) file ~9 s Error: Process failed to launch! (playwright-core/lib/server/electron/electron.js:247, waitForLine)
a .txt file ~60–65 s TimeoutError: Timeout 59999ms exceeded. (playwright-core/lib/server/progress.js:86)

Other invalid inputs are handled cleanly: a relative path, a bad action discriminator, and a nonexistent executable all give typed invalid_request errors.

Reproduction

  1. Create any minimal Electron-style app directory, e.g. C:\repro\app\package.json ({"name":"x","main":"main.js"}) and an empty main.js.
  2. Create a non-executable file: C:\repro\not-an-executable.txt containing plain text.
  3. MCP: start rea mcp, call binary_session {} (it succeeds), then call
    capture_electron_scenario {"executable_path":"C:\\repro\\not-an-executable.txt","application_path":"C:\\repro\\app","actions":[]}
  4. Observe the server process exit with code 1 (stderr below) instead of a JSON-RPC response.
  5. CLI: write the same arguments as BOM-free JSON to C:\repro\input.json and run rea capture-electron-scenario C:\repro\input.json --format json. Exit 1, uncaught stack, no JSON.

Swapping the .txt for any .har / JSON file reproduces the faster Process failed to launch! variant.

Expected behavior

The call returns a typed error, for example invalid_request on executable_path ("not an executable" / "not an Electron binary") before launch, or an execution_failure/launch_failed result with remediation if the launch itself fails. The server keeps running and all other sessions' state survives. CLI exit 1 with the standard JSON error envelope.

Impact and scope

  • Process isolation / availability: a single bad argument from any MCP client kills the shared server and silently discards every caller's active target, retained Evidence and unknowns. Because clients restart the server transparently, agents get no clear signal. They only see later "not retained in this session" errors. In our run this happened while other agents had live native sessions open.
  • Evidence integrity: Evidence IDs that callers already cited become unresolvable mid-investigation.
  • Not affected: the documented input validation for relative paths, a nonexistent executable and malformed actions.
  • The tool is annotated destructiveHint, so agents may legitimately probe it with candidate paths found on disk, which makes a wrong-file input realistic.

REA version

6.4.0 (npm, global install; bundled playwright-core 1.58.2)

Runtime and platform

Node 24.20.0; Windows 11 x64 (10.0.26300)

Target and artifact context

No real Electron target involved: a plain text file and a JSON (HAR) file passed as executable_path, with a minimal app directory as application_path.

Interface

MCP (also reproduces via CLI)

Earliest failure stage

Request parsing or validation

Relevant output or evidence

# isolated `rea mcp`, executable_path = <dir>\not-an-executable.txt
before: server alive, binary_session ok = true
capture_electron_scenario outcome: {"kind":"server-exited","code":1,"signal":null}
--- server stderr ---
<npm>\rea-agents\node_modules\playwright-core\lib\server\progress.js:86
      const timeoutError = new import_errors.TimeoutError(`Timeout ${timeout}ms exceeded.`);
TimeoutError: Timeout 59999ms exceeded.
    at ProgressController.run (...playwright-core\lib\server\progress.js:86:28)
    at ElectronDispatcher._runCommand (...playwright-core\lib\server\dispatchers\dispatcher.js:91:31)
    at DispatcherConnection.dispatch (...playwright-core\lib\server\dispatchers\dispatcher.js:325:39)
Node.js v24.20.0

# isolated `rea mcp`, executable_path = <dir>\capture.har
capture_electron_scenario outcome: {"kind":"server-exited","code":1,"signal":null}
<npm>\rea-agents\node_modules\playwright-core\lib\server\electron\electron.js:247
  const failError = new Error("Process failed to launch!");
Error: Process failed to launch!
    at waitForLine (...playwright-core\lib\server\electron\electron.js:247:21)
    at Electron.launch (...playwright-core\lib\server\electron\electron.js:195:32)
    at async ElectronDispatcher.launch (...playwright-core\lib\server\dispatchers\electronDispatcher.js:38:33)
    at async ProgressController.run (...playwright-core\lib\server\progress.js:98:22)
    at async ElectronDispatcher._runCommand (...playwright-core\lib\server\dispatchers\dispatcher.js:91:14)
    at async DispatcherConnection.dispatch (...playwright-core\lib\server\dispatchers\dispatcher.js:325:22)
Node.js v24.20.0

Typed outcome or recovery expected

invalid_request with issues[0].path = ["executable_path"] when the file is not a launchable executable (on Windows, for example, not a PE image / not .exe; on POSIX, not X_OK). Otherwise a typed launch failure carrying the Playwright message in details, with the server still serving requests.

Analysis or suspected cause

Source-derived hypothesis, not verified by instrumentation: dist/browser/PlaywrightElectronActiveProvider.js awaits electron.launch(...) inside a try/catch (lines ~40–84), so an ordinary rejection should be caught. The stacks above originate in playwright-core's in-process server dispatcher (ElectronDispatcher._runCommand / ProgressController.run). That suggests a second rejection raised there is not attached to the promise REA awaits, and Node's default --unhandled-rejections=throw then terminates the process. I found no process-level unhandledRejection / uncaughtException guard in dist/, so any such escape from a provider is fatal to the shared server.

Possible directions (maintainers' call): validate executable_path is a launchable executable before calling Playwright; contain provider-level unhandled rejections so one tool call cannot terminate the MCP server; or run active Electron launches in a disposable child process.

Additional context

  • Related, but a different symptom: #1589 (Windows Electron cleanup PID verification, closed).
  • Secondary, minor: with a nonexistent executable_path, the validation issue is attributed to application_path (issues: [{"path":["application_path"],"message":"Electron executable, application, and root must resolve to existing paths"}]) even though the application path exists. I can file that separately if preferred.
  • Found during a ground-truth test run of REA 6.4.0 on Windows. No standalone Electron runtime was installed, so the happy path of capture_electron_scenario was not exercised.
Originally created by @BeLit-Development-2 on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2269 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area MCP or agent integration ### Observed behavior `capture_electron_scenario` terminates the whole REA process when `executable_path` names an existing file that is not a launchable Electron executable. The request never gets a typed error back. - **MCP:** the `rea mcp` server exits with code 1 while the call is in flight. The client sees `Error: Connection closed`. Everything held by that server is gone: the active native target, retained Evidence, and the residual-unknown registry, including state belonging to other concurrent callers. In Claude Code the client restarted the server automatically (new PID), so later calls appeared to work but reported earlier Evidence IDs as "not retained in this session". - **CLI:** `rea capture-electron-scenario <input.json>` exits 1 with an uncaught Node stack trace on stderr and no JSON envelope. Two distinct uncaught errors from `playwright-core`, depending on the file: | `executable_path` points at | Time to crash | Uncaught error | |---|---|---| | a `.har` (JSON text) file | ~9 s | `Error: Process failed to launch!` (`playwright-core/lib/server/electron/electron.js:247`, `waitForLine`) | | a `.txt` file | ~60–65 s | `TimeoutError: Timeout 59999ms exceeded.` (`playwright-core/lib/server/progress.js:86`) | Other invalid inputs are handled cleanly: a relative path, a bad action discriminator, and a nonexistent executable all give typed `invalid_request` errors. ### Reproduction 1. Create any minimal Electron-style app directory, e.g. `C:\repro\app\package.json` (`{"name":"x","main":"main.js"}`) and an empty `main.js`. 2. Create a non-executable file: `C:\repro\not-an-executable.txt` containing `plain text`. 3. MCP: start `rea mcp`, call `binary_session {}` (it succeeds), then call `capture_electron_scenario {"executable_path":"C:\\repro\\not-an-executable.txt","application_path":"C:\\repro\\app","actions":[]}` 4. Observe the server process exit with code 1 (stderr below) instead of a JSON-RPC response. 5. CLI: write the same arguments as BOM-free JSON to `C:\repro\input.json` and run `rea capture-electron-scenario C:\repro\input.json --format json`. Exit 1, uncaught stack, no JSON. Swapping the `.txt` for any `.har` / JSON file reproduces the faster `Process failed to launch!` variant. ### Expected behavior The call returns a typed error, for example `invalid_request` on `executable_path` ("not an executable" / "not an Electron binary") before launch, or an `execution_failure`/`launch_failed` result with remediation if the launch itself fails. The server keeps running and all other sessions' state survives. CLI exit 1 with the standard JSON error envelope. ### Impact and scope - **Process isolation / availability:** a single bad argument from any MCP client kills the shared server and silently discards every caller's active target, retained Evidence and unknowns. Because clients restart the server transparently, agents get no clear signal. They only see later "not retained in this session" errors. In our run this happened while other agents had live native sessions open. - **Evidence integrity:** Evidence IDs that callers already cited become unresolvable mid-investigation. - Not affected: the documented input validation for relative paths, a nonexistent executable and malformed actions. - The tool is annotated `destructiveHint`, so agents may legitimately probe it with candidate paths found on disk, which makes a wrong-file input realistic. ### REA version 6.4.0 (npm, global install; bundled playwright-core 1.58.2) ### Runtime and platform Node 24.20.0; Windows 11 x64 (10.0.26300) ### Target and artifact context No real Electron target involved: a plain text file and a JSON (HAR) file passed as `executable_path`, with a minimal app directory as `application_path`. ### Interface MCP (also reproduces via CLI) ### Earliest failure stage Request parsing or validation ### Relevant output or evidence ```text # isolated `rea mcp`, executable_path = <dir>\not-an-executable.txt before: server alive, binary_session ok = true capture_electron_scenario outcome: {"kind":"server-exited","code":1,"signal":null} --- server stderr --- <npm>\rea-agents\node_modules\playwright-core\lib\server\progress.js:86 const timeoutError = new import_errors.TimeoutError(`Timeout ${timeout}ms exceeded.`); TimeoutError: Timeout 59999ms exceeded. at ProgressController.run (...playwright-core\lib\server\progress.js:86:28) at ElectronDispatcher._runCommand (...playwright-core\lib\server\dispatchers\dispatcher.js:91:31) at DispatcherConnection.dispatch (...playwright-core\lib\server\dispatchers\dispatcher.js:325:39) Node.js v24.20.0 # isolated `rea mcp`, executable_path = <dir>\capture.har capture_electron_scenario outcome: {"kind":"server-exited","code":1,"signal":null} <npm>\rea-agents\node_modules\playwright-core\lib\server\electron\electron.js:247 const failError = new Error("Process failed to launch!"); Error: Process failed to launch! at waitForLine (...playwright-core\lib\server\electron\electron.js:247:21) at Electron.launch (...playwright-core\lib\server\electron\electron.js:195:32) at async ElectronDispatcher.launch (...playwright-core\lib\server\dispatchers\electronDispatcher.js:38:33) at async ProgressController.run (...playwright-core\lib\server\progress.js:98:22) at async ElectronDispatcher._runCommand (...playwright-core\lib\server\dispatchers\dispatcher.js:91:14) at async DispatcherConnection.dispatch (...playwright-core\lib\server\dispatchers\dispatcher.js:325:22) Node.js v24.20.0 ``` ### Typed outcome or recovery expected `invalid_request` with `issues[0].path = ["executable_path"]` when the file is not a launchable executable (on Windows, for example, not a PE image / not `.exe`; on POSIX, not `X_OK`). Otherwise a typed launch failure carrying the Playwright message in `details`, with the server still serving requests. ### Analysis or suspected cause *Source-derived hypothesis, not verified by instrumentation:* `dist/browser/PlaywrightElectronActiveProvider.js` awaits `electron.launch(...)` inside a `try/catch` (lines ~40–84), so an ordinary rejection should be caught. The stacks above originate in playwright-core's in-process server dispatcher (`ElectronDispatcher._runCommand` / `ProgressController.run`). That suggests a second rejection raised there is not attached to the promise REA awaits, and Node's default `--unhandled-rejections=throw` then terminates the process. I found no process-level `unhandledRejection` / `uncaughtException` guard in `dist/`, so any such escape from a provider is fatal to the shared server. Possible directions (maintainers' call): validate `executable_path` is a launchable executable before calling Playwright; contain provider-level unhandled rejections so one tool call cannot terminate the MCP server; or run active Electron launches in a disposable child process. ### Additional context - Related, but a different symptom: #1589 (Windows Electron cleanup PID verification, closed). - Secondary, minor: with a *nonexistent* `executable_path`, the validation issue is attributed to `application_path` (`issues: [{"path":["application_path"],"message":"Electron executable, application, and root must resolve to existing paths"}]`) even though the application path exists. I can file that separately if preferred. - Found during a ground-truth test run of REA 6.4.0 on Windows. No standalone Electron runtime was installed, so the happy path of `capture_electron_scenario` was not exercised.
bsakaguchi 2026-10-15 18:27:57 +00:00
Author
Owner

@matbrgz commented on GitHub (Oct 12, 2026):

Triage & Error Handling Review

An uncaught exception in capture_electron_scenario causing the entire MCP process to crash and terminate in-flight sessions violates REA's server containment invariants:

  • Root Cause: Launching a non-executable file via child_process.spawn emits an asynchronous 'error' event on the ChildProcess instance (typically EACCES or ENOEXEC). If no 'error' listener is attached synchronously to the spawned child, Node.js escalates it to an unhandled exception (uncaughtException), terminating the Node process.
  • Fix: Ensure all spawn wrappers in src/process/ and Electron scenario runners attach an immediate 'error' handler, converting spawn failures into typed execution_failure results returned gracefully to the MCP client.
<!-- gh-comment-id:6134342249 --> @matbrgz commented on GitHub (Oct 12, 2026): ### Triage & Error Handling Review An uncaught exception in `capture_electron_scenario` causing the entire MCP process to crash and terminate in-flight sessions violates REA's server containment invariants: - **Root Cause:** Launching a non-executable file via `child_process.spawn` emits an asynchronous `'error'` event on the ChildProcess instance (typically `EACCES` or `ENOEXEC`). If no `'error'` listener is attached synchronously to the spawned child, Node.js escalates it to an unhandled exception (`uncaughtException`), terminating the Node process. - **Fix:** Ensure all spawn wrappers in `src/process/` and Electron scenario runners attach an immediate `'error'` handler, converting spawn failures into typed `execution_failure` results returned gracefully to the MCP client.
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Addressed on main in 97aff2820.

Electron launch rejects non-executable paths and contains launch rejections.

Validation: Four compiled CLI/MCP invalid-path and early-exit cases passed; package verification passed.

Verification scope: No real Electron target run.

<!-- gh-comment-id:6158652054 --> @morluto commented on GitHub (Oct 14, 2026): Addressed on main in [97aff2820](https://github.com/morluto/rea/commit/97aff2820). Electron launch rejects non-executable paths and contains launch rejections. Validation: Four compiled CLI/MCP invalid-path and early-exit cases passed; package verification passed. Verification scope: No real Electron target run.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#750
No description provided.