fix(desktop): guard broadcasts by renderer frame - #5286
Conversation
Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
hqhq1025
left a comment
There was a problem hiding this comment.
Technical GO on exact head 99135613c7c9ee00dcbd56ab3865bda4231fc707; I found no P0-P2 issues. One P3 test-coverage gap is noted inline.
The implementation fixes the actual ownership boundary: each broadcast captures the current WebFrameMain, checks that exact renderer generation, and sends through the same object. A real Electron 43.4.1/Xvfb stress probe completed 14,100 broadcasts across forcefullyCrashRenderer() and reload without the prior Error sending from webFrameMain, while broadcasts resumed after a live replacement frame appeared.
Review conclusions:
- The solution is optimal for the reported problem and stays at the shared broadcast boundary.
- Production code to delete: none identified.
- Tests to delete: none; the existing broadcast test should be strengthened as described inline.
- No deeper refactor is required.
- The revision is technically merge-ready; the P3 is non-blocking, and the final decision remains with a maintainer.
- Residual gaps: the exact crash/reload behavior was exercised on Linux/Xvfb, not native Windows or macOS.
This changes renderer-lifecycle IPC delivery behavior, so independent human review is required.
Verification passed build:test, full typecheck/lint/format, ASF headers, changed-file Biome, git diff --check, focused Desktop lifecycle tests 36/36, the real Electron crash/reload probe, all hosted checks, and a clean merge with current main bf6e9422960401da6bba01d03a8ea9979722bb5a. The full Desktop run was 2470 passed / 8 cancelled; those cancellations are in unchanged current-main MCP OAuth deadline tests and are not attributable to this two-file change.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
| } as unknown as Electron.WebFrameMain; | ||
| const renderer = Object.assign(new EventEmitter(), { | ||
| isDestroyed: () => false, | ||
| mainFrame, |
There was a problem hiding this comment.
[P3] Exercise the unavailable-frame branch instead of only adapting the mock
This test now supplies mainFrame.send, but both WebContents and the frame stay permanently live. It therefore does not verify the defect being fixed: a non-destroyed WebContents whose mainFrame is destroyed or throws during renderer replacement, followed by a live replacement frame. For example, removing the frame.isDestroyed() check while keeping mainFrame.send() would leave this test green. Please drive the same registered renderer through destroyed-frame, accessor-throw, and replacement-live states, asserting no delivery/no throw during the gap and delivery after recovery.
There was a problem hiding this comment.
Automated Codex follow-up: I added the requested regression coverage in 4982416. The existing auxiliary-renderer test now drives the same registered renderer through a destroyed main frame, a mainFrame accessor that throws during replacement, and a live replacement. It verifies that both unavailable phases neither throw nor deliver, then confirms delivery resumes after replacement.
Validation:
npm run buildnode --test --test-force-exit apps/desktop/dist/main/__tests__/workhub-presentation.test.js(29 passed)npm run lint,npm run format:check,npm run typechecknpx knip --workspace apps/desktop,npx knip --workspace packages/ui
I also temporarily bypassed the frame-destruction guard: the new no-delivery assertion failed as intended; restoring the guard returned the suite to 29/29.
Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
c266eef to
4982416
Compare
hqhq1025
left a comment
There was a problem hiding this comment.
Technical GO on exact head 49824162834d0dc8b4d0af2bd0b4d25c4c1bf83c; I found no remaining P0-P3 issues.
The new test closes the previous coverage gap. It now verifies all three relevant renderer-generation states at the shared broadcast boundary: a destroyed WebFrameMain is skipped, a transient webContents.mainFrame access failure is contained, and delivery resumes exactly once after a live replacement frame appears. The production implementation is byte-identical to the previously reviewed head, so the earlier Electron 43.4.1/Xvfb stress result still applies: 14,100 broadcasts across forcefullyCrashRenderer() and reload completed without Error sending from webFrameMain, and delivery resumed after recovery. The current exact head also passed the real WorkHub crash/recovery Electron journey.
Review conclusions:
- The solution is optimal for the reported lifecycle race and remains localized at the shared broadcast boundary.
- Production code to delete: none identified.
- Tests to delete or replace: none; the added regression directly exercises the formerly missing states.
- No deeper refactor is required.
- The revision is technically merge-ready; GitHub remains
REVIEW_REQUIRED, so the final decision belongs to an independent maintainer. - Residual gaps: the crash/recovery path was exercised on Linux/Xvfb, not native Windows or macOS.
This changes renderer-lifecycle IPC delivery behavior, so independent human review is required.
Verification passed build:test, full typecheck/lint/format, ASF headers, changed-file Biome, renderer architecture 112/112, E2E budget 38, focused WorkHub presentation 29/29, the real Electron WorkHub crash/recovery E2E 1/1, hosted test/package, git diff --check, and a clean merge with current main f32cf2b482a17f235b92e2775ed2672c2775e219. The full Desktop run was 2470 passed / 8 cancelled; the same eight cancellations reproduce in the unchanged MCP OAuth deadline suite when run alone and are not attributable to this two-file change.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
Summary
WebFrameMainWebContents.isDestroyed()is insufficient during renderer-process loss and navigation because theWebContentscan outlive its current main frame. Capturing the frame before checkingisDestroyed()avoids askingWebContents.send()to resolve a disposed generation internally.Fixes #5135
Verification
npm run buildnode --test --test-force-exit apps/desktop/dist/main/__tests__/workhub-presentation.test.js apps/desktop/dist/main/__tests__/main-startup-lifetime.test.js(36 passed)npm run lintnpm run format:checknpm run typechecknpx knip --workspace apps/desktopnpx knip --workspace packages/uinpx biome check apps/desktop/src/main/main-window.ts apps/desktop/src/main/__tests__/workhub-presentation.test.tsgit diff --checkAI use
Select exactly one:
Tool(s) and scope: Codex traced the Renderer recovery path and implemented the frame-generation broadcast guard.
Checklist
Does this PR entail a change in behavior?