[GH-ISSUE #4390] [Bug] Electron active hook keeps ipcMain listeners after removeListener and records once invocations twice #1744

Closed
opened 2026-10-16 21:09:46 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Adolanium on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4390

Before submitting

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

Area

Process capture or runtime observation

Observed behavior

With the Electron active hook loaded, ipcMain.removeListener(channel, listener) and ipcMain.off(channel, listener) do not remove a listener that was registered with ipcMain.on or ipcMain.once. The listener keeps firing, and repeated register/remove cycles pile up listeners.

ipcMain.once listeners are also wrapped twice, so each call is recorded as two main-event-invocation events.

Reproduction

  1. Load scripts/electron-active-hook.cjs with -r in a Node process that uses a stub electron module whose ipcMain is an EventEmitter. The existing test fixture in tests/boundary/browser/electronActiveHook.test.ts has one.
  2. Run ipcMain.on("repeated", listener) twice, then ipcMain.removeListener("repeated", listener) and ipcMain.off("repeated", listener).
  3. Run ipcMain.once("pending-once", listener) and ipcMain.removeListener("pending-once", listener).
  4. Emit both channels and read listenerCount and __reaElectronActiveSnapshot().

Expected behavior

The hook should only observe. After removal, the listener counts should be 0 and the listener should not run, as without the hook. One once call should be one recorded invocation.

Impact and scope

During capture_electron_scenario, an app that removes IPC listeners keeps running handlers it removed, which can change app behavior and add invocation events that the unhooked app would not produce. once invocations are double-counted in the IPC timeline. ipcMain.handle and removeHandler are not affected.

REA version

6.5.0, revision 8ab502439

Runtime and platform

Node 24 on Windows 11. The hook code is platform-independent.

Target and artifact context

Electron active capture hook. Stub electron module only. I did not run a real Electron app.

Interface

MCP

Earliest failure stage

Capture or observation

Relevant output or evidence

With two on registrations of one function, then removeListener and off; a removed pending once; and an on plus a fired once of one function followed by removeListener:

  • calls: 6 (expected 2)
  • listener counts after removal: [2, 1, 2] (expected [0, 0, 0])
  • listeners left after the first removeListener of the doubly registered function: 2 (expected 1)
  • each once call recorded twice as main-event-invocation

Typed outcome or recovery expected

Removal through the original function should remove the most recent matching registration, and each call should be recorded once.

Analysis or suspected cause

Source-derived. patchIpcMain in scripts/electron-active-hook.cjs registers a new wrapper function for each listener passed to on and once and does not patch removeListener or off, so removal looks for the original function, which is not registered. EventEmitter#once calls this.on internally, and the patched on wraps the listener a second time.

Additional context

Fix is in https://github.com/morluto/rea/pull/4389.

Originally created by @Adolanium on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4390 ### 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 Process capture or runtime observation ### Observed behavior With the Electron active hook loaded, `ipcMain.removeListener(channel, listener)` and `ipcMain.off(channel, listener)` do not remove a listener that was registered with `ipcMain.on` or `ipcMain.once`. The listener keeps firing, and repeated register/remove cycles pile up listeners. `ipcMain.once` listeners are also wrapped twice, so each call is recorded as two `main-event-invocation` events. ### Reproduction 1. Load `scripts/electron-active-hook.cjs` with `-r` in a Node process that uses a stub `electron` module whose `ipcMain` is an `EventEmitter`. The existing test fixture in `tests/boundary/browser/electronActiveHook.test.ts` has one. 2. Run `ipcMain.on("repeated", listener)` twice, then `ipcMain.removeListener("repeated", listener)` and `ipcMain.off("repeated", listener)`. 3. Run `ipcMain.once("pending-once", listener)` and `ipcMain.removeListener("pending-once", listener)`. 4. Emit both channels and read `listenerCount` and `__reaElectronActiveSnapshot()`. ### Expected behavior The hook should only observe. After removal, the listener counts should be 0 and the listener should not run, as without the hook. One `once` call should be one recorded invocation. ### Impact and scope During `capture_electron_scenario`, an app that removes IPC listeners keeps running handlers it removed, which can change app behavior and add invocation events that the unhooked app would not produce. `once` invocations are double-counted in the IPC timeline. `ipcMain.handle` and `removeHandler` are not affected. ### REA version 6.5.0, revision `8ab502439` ### Runtime and platform Node 24 on Windows 11. The hook code is platform-independent. ### Target and artifact context Electron active capture hook. Stub `electron` module only. I did not run a real Electron app. ### Interface MCP ### Earliest failure stage Capture or observation ### Relevant output or evidence With two `on` registrations of one function, then `removeListener` and `off`; a removed pending `once`; and an `on` plus a fired `once` of one function followed by `removeListener`: - calls: 6 (expected 2) - listener counts after removal: `[2, 1, 2]` (expected `[0, 0, 0]`) - listeners left after the first `removeListener` of the doubly registered function: 2 (expected 1) - each `once` call recorded twice as `main-event-invocation` ### Typed outcome or recovery expected Removal through the original function should remove the most recent matching registration, and each call should be recorded once. ### Analysis or suspected cause Source-derived. `patchIpcMain` in `scripts/electron-active-hook.cjs` registers a new wrapper function for each listener passed to `on` and `once` and does not patch `removeListener` or `off`, so removal looks for the original function, which is not registered. `EventEmitter#once` calls `this.on` internally, and the patched `on` wraps the listener a second time. ### Additional context Fix is in https://github.com/morluto/rea/pull/4389.
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#1744
No description provided.