[GH-ISSUE #8146] [Bug] capture_electron_scenario's active hook rethrows unhandled rejections, so an app that would only log a warning blocks and the scenario hangs until cancelled #4688

Closed
opened 2026-10-31 00:52:29 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Rinkia on GitHub (Oct 30, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8146

Before submitting

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

Area

Electron

Observed behavior

The main-process hook in scripts/electron-active-hook.cjs (around lines 1270–1283) registers:

process.once("unhandledRejection", (reason, promise) => {
  recordRuntime("process-lifecycle", "unhandled-rejection", "observed", { … });
  throw reason;
});

In Electron's main process, an unhandled rejection is only a warning. I checked this on Electron 44.7.0 without REA:

  • An IPC handler that runs Promise.reject(new Error("probe-reject")) prints UnhandledPromiseRejectionWarning.
  • The app keeps running and responds to later IPC.

Under capture-electron-scenario, the hook turns the same rejection into an uncaught exception. The main process then stops responding:

App action after the rejection Without REA Under capture-electron-scenario
A wait of 1500 ms, then a click that sends IPC Works The wait never finishes. It stays pending until I cancel it after 57–87 s. The later click never runs.
The 6.8.0 release, same scenario – Same hang.
main at 45cc93596 – Same hang.
main with the two rethrows in that block removed – Completes: wait 1504 ms, then the click. The timeline records process-lifecycle unhandled-rejection. The IPC events ext and ping are recorded.

The cancelled result keeps only the initial snapshot. So the IPC events and the unhandled-rejection record that the hook did collect are not in the partial observation (ipc is empty).

In another run, the next click failed with locator.click: Target page, context or browser has been closed. That gave an execution_failure that says "Run rea doctor".

This also affects REA's own shell block. blockedShellResults.openExternal returns Promise.reject(...), which is documented. An app that calls shell.openExternal(url) without a catch is common, and such an app hangs under capture for that reason alone. My probe app did this. When I added .catch() to the same call, the capture completed and recorded shell-attempt openExternal blocked.

Expected behavior

  • The hook records the rejection without changing what the process does with it. For example, it should not rethrow from the unhandledRejection listener. It could instead reproduce the runtime's default, which in Electron's main process is a warning.
  • A blocked shell call should not make an otherwise working target unusable.

Reproduction

Container: Linux x64 under Xvfb. Electron 44.7.0 from the official release, checksum-verified, started with --no-sandbox. Seen on the 6.8.0 release and on main at 45cc93596.

  1. Create a minimal app with a BrowserWindow, a preload that exposes ipcRenderer.send("ext"), and this handler in the main process:
    ipcMain.on("ext", () => { Promise.reject(new Error("probe-reject")); });
    
    A page button #ext sends it, and #ping invokes another handler.
  2. Run rea capture-electron-scenario scenario.json --format json with:
    {
      "executable_path": "/opt/electron/electron",
      "application_path": "/tmp/app/main.js",
      "application_root": "/tmp/app",
      "args": ["--no-sandbox"],
      "actions": [
        {"step_id": "ext", "kind": "click", "selector": "#ext", "window_index": 0},
        {"step_id": "w", "kind": "wait", "duration_ms": 1500, "window_index": 0},
        {"step_id": "ping", "kind": "click", "selector": "#ping", "window_index": 0}
      ]
    }
    
  3. ext completes, and w never finishes. Send SIGTERM: the result is cancelled, and w is listed as cancelled after the whole wait.
  4. Run the same app with electron --remote-debugging-port=9223 without REA and trigger #ext. The app logs the warning and keeps working.

Additional context

The neighbouring uncaughtException listener rethrows too. I didn't test whether that changes Electron's default handling of an uncaught exception.

Originally created by @Rinkia on GitHub (Oct 30, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8146 ### 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 Electron ### Observed behavior The main-process hook in `scripts/electron-active-hook.cjs` (around lines 1270–1283) registers: ```js process.once("unhandledRejection", (reason, promise) => { recordRuntime("process-lifecycle", "unhandled-rejection", "observed", { … }); throw reason; }); ``` In Electron's main process, an unhandled rejection is only a warning. I checked this on Electron 44.7.0 without REA: - An IPC handler that runs `Promise.reject(new Error("probe-reject"))` prints `UnhandledPromiseRejectionWarning`. - The app keeps running and responds to later IPC. Under `capture-electron-scenario`, the hook turns the same rejection into an uncaught exception. The main process then stops responding: | App action after the rejection | Without REA | Under `capture-electron-scenario` | |---|---|---| | A `wait` of 1500 ms, then a click that sends IPC | Works | The `wait` never finishes. It stays pending until I cancel it after 57–87 s. The later click never runs. | | The 6.8.0 release, same scenario | – | Same hang. | | `main` at `45cc93596` | – | Same hang. | | `main` with the two rethrows in that block removed | – | Completes: `wait` 1504 ms, then the click. The timeline records `process-lifecycle unhandled-rejection`. The IPC events `ext` and `ping` are recorded. | The cancelled result keeps only the initial snapshot. So the IPC events and the `unhandled-rejection` record that the hook did collect are not in the partial observation (`ipc` is empty). In another run, the next click failed with `locator.click: Target page, context or browser has been closed`. That gave an `execution_failure` that says "Run `rea doctor`". This also affects REA's own shell block. `blockedShellResults.openExternal` returns `Promise.reject(...)`, which is documented. An app that calls `shell.openExternal(url)` without a `catch` is common, and such an app hangs under capture for that reason alone. My probe app did this. When I added `.catch()` to the same call, the capture completed and recorded `shell-attempt openExternal blocked`. ### Expected behavior - The hook records the rejection without changing what the process does with it. For example, it should not rethrow from the `unhandledRejection` listener. It could instead reproduce the runtime's default, which in Electron's main process is a warning. - A blocked shell call should not make an otherwise working target unusable. ### Reproduction Container: Linux x64 under Xvfb. Electron 44.7.0 from the official release, checksum-verified, started with `--no-sandbox`. Seen on the 6.8.0 release and on `main` at `45cc93596`. 1. Create a minimal app with a `BrowserWindow`, a preload that exposes `ipcRenderer.send("ext")`, and this handler in the main process: ```js ipcMain.on("ext", () => { Promise.reject(new Error("probe-reject")); }); ``` A page button `#ext` sends it, and `#ping` invokes another handler. 2. Run `rea capture-electron-scenario scenario.json --format json` with: ```json { "executable_path": "/opt/electron/electron", "application_path": "/tmp/app/main.js", "application_root": "/tmp/app", "args": ["--no-sandbox"], "actions": [ {"step_id": "ext", "kind": "click", "selector": "#ext", "window_index": 0}, {"step_id": "w", "kind": "wait", "duration_ms": 1500, "window_index": 0}, {"step_id": "ping", "kind": "click", "selector": "#ping", "window_index": 0} ] } ``` 3. `ext` completes, and `w` never finishes. Send SIGTERM: the result is `cancelled`, and `w` is listed as `cancelled` after the whole wait. 4. Run the same app with `electron --remote-debugging-port=9223` without REA and trigger `#ext`. The app logs the warning and keeps working. ### Additional context The neighbouring `uncaughtException` listener rethrows too. I didn't test whether that changes Electron's default handling of an uncaught exception.
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#4688
No description provided.