[Bug]: Desktop app can still crash when a preview tab's renderer dies mid-screenshot, until Electron ships Chromium 157#24094

opened9d agobySshivam

[Opus 5.5][Claude Code] on behalf of shivam.

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/desktop

What happened

#24026 reported the desktop app crashing (SIGSEGV at 0x0 in the main process) when a preview tab's renderer died while an agent was taking a snapshot of it. The cause is a Chromium bug, not T3 code:

  • An agent snapshot ends in a CDP Page.captureScreenshot on the guest. Chromium copies the tab's surface for it.
  • If the guest's renderer dies while that copy is in flight, the copy fails. RenderWidgetHostImpl::OnSnapshotFromSurfaceReceived retries through GetView()->CopyFromSurface without checking GetView(), which is null once the renderer is gone. PageHandler::ScreenshotCaptured has the same unchecked GetView() when a clip is set, and our snapshots send a clip when they downscale.
  • Chromium fixed both in 157: crbug.com/567321049, commit f49f3e5. We ship Electron 44.9.3, and the newest stable Electron (45.0.0) is on Chromium 156, so every build today can hit it.

I reproduced it on Linux with Electron 44.9.3. A harness loaded the real DesktopBrowserHost.ts into Electron, kept agent screenshots in flight, and SIGKILLed the guest renderer. The crash symbolicated to the same frame as #24026.

What #24056 changed, and why it isn't the whole fix

T3 made the crash much likelier than it needed to be. A guest could have several screenshots copying at once, because snapshots overlapped and because a snapshot the server timed out on keeps running inside Chromium (CDP commands can't be cancelled). Every copy in flight is another chance to be mid-copy when the renderer dies.

#24056 sends each guest one screenshot at a time. The queue moves on when the page navigates or reloads, since a pending capture can go unanswered across a new document, and screenshots left by a released relay are dropped. In the stress harness, crashes per renderer kill went from 9 of 32 (about 28%) on main to 20 of 774 (about 2.6%), and the rate rises with machine load.

One copy can still be in flight when a renderer dies, so the crash is rarer but still possible. That's what this issue tracks.

Expected behavior

A preview tab whose renderer dies during a snapshot reports a crashed tab, and the app keeps running.

Actual behavior

Since #24056 this is rarer, but the desktop main process can still segfault, taking the bundled server with it.

What would fully fix it

Either one:

  1. Bump apps/desktop to an Electron release on Chromium 157 or newer once one is stable.
  2. Ask Electron to backport f49f3e5 to the 44-x-y and 45-x-y branches, then take that patch release.

Once Electron has the fix, the one-at-a-time queue in DesktopBrowserHost.ts can stay, since it keeps abandoned captures from piling up, or it can go.

Impact

Minor bug or occasional failure

Version or commit

main at cccb78c459, after #24056 (Electron 44.9.3)

Environment

Desktop app. Reproduced on Linux (Xvfb, Electron 44.9.3); originally reported on macOS arm64 (Electron 44.4.5).

Workaround

None in the app. Agents taking fewer snapshots of a page that is about to run out of memory makes it less likely.

Synced from GitHub

[Opus 5.5][Claude Code] on behalf of SKCarlsson.

Hit again on macOS with #24056 included. The evidence suggests the capture that crashed the app had been stuck for about a day, so the risk lasts longer than "while a screenshot is in flight".

The crash

  • Build: packaged 0.1.3-nightly.20261023.3579 (Electron 44.9.3), macOS 27.0.1 arm64. The bundle contains #24056's one-screenshot-per-guest queue.
  • Symbolicated with Electron's v44.9.3 Breakpad symbols (module 4C4C442D55553144A1FC8F505C7CA8700, which matches the crash report). It's the same frame as #24026:
1CrBrowserMain EXC_BAD_ACCESS (SIGSEGV) KERN_INVALID_ADDRESS at 0x0
2#0 content::RenderWidgetHostImpl::OnSnapshotFromSurfaceReceived render_widget_host_impl.cc:3917
3#1 RenderWidgetHostViewChildFrame::CopyFromSurface(...)::$_0 (callback)
4#3 base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork()
5#6 base::MessagePumpCFRunLoopBase::RunWorkSource(void*)

The faulting instruction is ldr x8, [x21] with x21 = 0 (the null GetView()), followed by a vtable call.

No screenshot was being taken

The retained server traces cover the ~50 minutes before the crash and contain no preview automation calls. The only preview spans are PreviewAutomationBroker.focusHost and PreviewManager.close at 21:05:51.65, after the main process had already died.

The pending capture was probably ~26 hours old

macOS powerd shows the main process holding a "Capturing" NoDisplaySleepAssertion for the whole session:

12026-10-23 19:28:53 preview guest renderer starts (pid 9439, surface "preview" in renderer-history.ndjson)
22026-10-23 19:29:04 powerd: Created NoDisplaySleepAssertion "Capturing" id:21474876784
32026-10-24 21:02:21 guest renderer 9439 still alive (last renderer-history sample)
42026-10-24 21:05:51 main process SIGSEGV
52026-10-24 21:05:51 powerd: ClientDied NoDisplaySleepAssertion "Capturing" age:25:36:46

In Chromium 152 the only DevTools path that takes this lock is Page.captureScreenshot: IncrementCapturerCount(stay_awake=true) at page_handler.cc:1432. The handle lives in PendingScreenshotRequest until the snapshot callback runs. Page.startScreencast takes no capturer handle, and neither does DevToolsVideoConsumer. Outside DevTools, a tab-capture stream (the getDisplayMedia recording path) holds the same lock. A lock held for 25.6 hours plus a crash in the screenshot callback with no screenshot requested points to one Page.captureScreenshot that stalled on 2026-10-23 and stayed pending until the guest's renderer went away. The desktop and server traces from 19:29 on 2026-10-23 have already rotated out, so I couldn't see which call started it.

What this means for this issue

  • The crash can happen long after any screenshot is requested. A stalled capture stays pending indefinitely and crashes the app whenever that guest's renderer exits, here about a day later. #24056's queue lets later screenshots get past a stuck one, but the stuck one stays pending in Chromium until that renderer goes away.
  • #17870 and #17180 (leaked "Capturing" wake lock) are probably this same stuck screenshot, not a screencast. While it lasts, the Mac's display can't sleep.
  • An app-side check is available: a guest with no screenshot in flight on T3's side whose webContents.isBeingCaptured() is still true is carrying a stuck capture (untested).
  • Chromium 152 notes why captures stall, next to the kCDPScreenshotNewSurface check: "When view is completely hidden, capturing a surface snapshot will stall because the surface is never presented." Not sending Page.captureScreenshot to a hidden guest, and using capturePage() instead, would avoid creating stuck captures until Electron ships Chromium 157.

I couldn't see what ended the guest renderer at 21:05:51 because macOS doesn't log renderer exits. A T3 utility helper exited and T3's audio session stopped about 100 ms before the main process died.