Desktop transcript consumers survive Renderer loss and accumulate WebContents listeners
- Dominant language
- TypeScript
- Stars
- 5.4k
- Forks
- 502
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 716
Description
## What happened
Desktop transcript registrations are cleaned up when their target `WebContents` emits `destroyed`, but main Renderer crash/reload replaces the renderer frame/process without destroying that `WebContents`. Registrations owned by the retired renderer generation therefore remain in `RuntimeHostSessionObservationRegistry`.
When the recovered Renderer opens a transcript with a new consumer ID, another `once('destroyed')` listener is added to the same `WebContents`. Repeated recovery/reload cycles accumulate transcript registrations and listeners until Node reports:
```text
MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 destroyed listeners added to [WebContents].
```
Expected behavior: transcript consumers should be retired when the renderer document/process that created them is gone. Recovery should not retain obsolete Host transcript consumers or accumulate `destroyed` listeners, and cleanup must not close consumers belonging to the replacement renderer generation.
## How to reproduce
The following was verified with Electron and the repository's `RuntimeHostSessionObservationRegistry`:
1. Create a hidden `BrowserWindow` and load a page.
2. Create 11 distinct transcript consumers through `RuntimeHostSessionObservationRegistry.openTranscript(...)`, all targeting that window's `webContents` (the same target used by `sessions:transcript:open`).
3. Record `webContents.listenerCount('destroyed')`.
4. Call `webContents.forcefullyCrashRenderer()` and await `render-process-gone`.
5. Load a replacement page into the same `WebContents` and record the listener count again.
Observed:
```json
{
"webContentsDestroyed": false,
"destroyedEvents": 0,
"listenersBeforeCrash": 13,
"listenersAfterCrash": 13,
"listenersAfterReload": 13,
"destroyedEventsAfterReload": 0,
"listenersAfterRegistryClose": 2
}
```
The two non-transcript listeners are the harness baseline. The 11 listeners added by `openTranscript()` survived both renderer-process loss and replacement-page load; explicit registry close removed them. `--trace-warnings` points the warning directly to `RuntimeHostSessionObservationRegistry.openTranscript`.
## Environment
- Maka commit: `a9b4f21afb17e0ef310d076382e69ddda3a441d7`
- Electron: `43.4.1`
- Original observation: Windows source checkout, Desktop, `npm run dev`
- Deterministic verification: Linux x86_64 (`7.0.0-30-generic`), Desktop under Xvfb
- Node.js: `v26.3.0`
## Logs, screenshots, or additional context
Current-main ownership chain:
- `runtime-host-session-execution-ipc-main.ts:204-211` passes `event.sender` as the transcript target.
- `runtime-host-session-observation-registry.ts:356-380` stores the registration and attaches `target.once('destroyed', ...)`.
- Normal close and registry close remove that listener (`:462-493`, `:641-646`).
- Main Renderer loss is observed as `render-process-gone` and recovery reloads the existing main window (`main-window.ts:220-235`, `runtime-host-boot.ts:508-528`), but transcript registrations have no equivalent renderer-generation cleanup.
- Preload allocates a fresh random consumer ID for each `transcripts.open()`, so a replacement renderer cannot reuse or explicitly close the retired generation's consumer by identity.
This is independent from the stderr flood caused by broadcasts targeting a disposed frame; it needs its own lifecycle cleanup and regression coverage.
Contributor guide
Research direction
Start with runtime-host-session-execution-ipc-main.ts:204-211 and runtime-host-session-observation-registry.ts:356-380, then trace renderer-loss recovery in main-window.ts:220-235 and runtime-host-boot.ts:508-528. Reproduce the crash and reload sequence with openTranscript() and inspect listener counts. Done means retired renderer consumers and their destroyed listeners are cleaned up without closing consumers belonging to the replacement renderer generation, with regression coverage for repeated recovery.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- electron, typescript
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100