Skip to content

[Bug]: macOS main process aborts with V8 OOM in JSON.stringify while relaying preview CDP events (SIGTRAP) #20395

Open

Description

@Maxymvs

Before submitting

Area

apps/desktop (preview CDP relay, main process)

Steps to reproduce

The whole desktop app crashed twice in 42 seconds while an agent drove the built-in browser against a local React + Vite dev app. In both cases:

  1. The agent calls preview_open on http://localhost:5174/query. The tab opens hidden (visible: false), and the call succeeds.
  2. The agent calls preview_evaluate with a short DOM query. The second crash's expression also waits 3 seconds first.
  3. The main process aborts 4 to 9 seconds later. That preview_evaluate call never gets a response.

The second crash happened 21 seconds after relaunch, so this is not a slow leak over a long session. A single large message seems to be enough.

Timeline from the traces (local time, -0700):

Crash 1 Crash 2
App launched 15:07:43 15:40:20
Last completed preview call preview_snapshot (includeImage: true), 15:39:49 preview_open, 15:40:28 to 15:40:30
In-flight call preview_evaluate, sent 15:39:54 preview_evaluate, sent 15:40:32
Crash 15:39:58 15:40:41

Before crash 1, a preview_click on the same page also timed out after 15000 ms. The page was busy during that run.

Expected behavior

A large CDP event from a preview tab should be dropped, truncated, or turned into a typed error. It should not abort the desktop process.

Actual behavior

V8 hits a fatal out-of-memory error inside JSON.stringify in the main process and aborts with EXC_BREAKPOINT (SIGTRAP). The desktop window and the bundled server go down together, and every running agent thread loses its turn.

Impact

Blocks work. The agent can't do browser testing on this page, and each crash takes down every other thread in the app.

Version or commit

Environment

macOS 27.0.1 (26A434), Apple silicon (Mac16,7). The app was in the foreground.

Logs or stack traces

I symbolicated both reports with the official electron-v44.4.5-darwin-arm64-symbols.zip. Both crashes have the same stack. Crash 2, thread 0 (CrBrowserMain):

0  partition_alloc::internal::OnNoMemoryInternal(unsigned long) +0x18
2  partition_alloc::internal::OnNoMemory(unsigned long)
3  partition_alloc::internal::OnErrnoNoMem()
5  v8::Utils::ReportOOMFailure(...)
6  v8::internal::V8::FatalProcessOutOfMemory(...)
7  v8::internal::Zone::Expand(unsigned long)
8  v8::internal::Zone::ExpandAndAllocate(unsigned long)
9  v8::internal::OutBuffer<unsigned short>::Extend(unsigned long)
10 v8::internal::FastJsonStringifier<unsigned short>::SerializeObject(...)
11 v8::internal::FastJsonStringifier<unsigned short>::ResumeFrom<unsigned char>(...)
12 v8::internal::JsonStringify(...)
13 v8::internal::Builtin_JsonStringify(...)
14-19 <JIT JavaScript frames>
20 v8::internal::Execution::Call(...)
21 v8::Function::Call(...)
22 node::InternalMakeCallback(...)
24 gin_helper::internal::CallMethodWithArgs(...)
25 gin_helper::EmitEvent<...>
26 gin_helper::EventEmitterMixin<electron::api::Debugger>::Emit<...>
27 electron::api::Debugger::DispatchProtocolMessage(...)
28 content::DevToolsSession::DispatchProtocolResponseOrNotification(...)
29 content::DevToolsSession::DispatchProtocolNotification(...)
30 blink::mojom::DevToolsSessionHostStubDispatch::Accept(...)
...

So a CDP notification from the preview tab reaches a webContents.debugger "message" listener, and that listener calls JSON.stringify on a value large enough to exhaust V8's zone. The OOM is a fatal V8 error, so JS can't catch it.

The bundled apps/desktop/dist-electron/main.cjs registers two debugger.on("message", ...) listeners. The control-session listener in PreviewManager.ensureControlSession doesn't stringify. The CDP relay listener (tab.onMessage → relay.event → send) does, for every event it forwards:

const send = (message) => write(JSON.stringify(message));

It sends every non-download event once per attached page session, with no size check. That makes the relay the most likely caller. The JIT frames aren't symbolicated, so I can't prove it from the stack alone.

I don't know which CDP event is oversized. The page is a large React app served by the Vite dev server.

Possible fixes, from narrowest to broadest:

  • Check the size of params before calling JSON.stringify in the relay, and drop or replace oversized events with a typed notice.
  • Have the relay forward only the domains the server-side Playwright actually uses.
  • Move the serialization out of the main process, so a fatal error kills a utility process instead of the whole app.

I can share both .ips reports and the matching desktop.trace.ndjson / server.trace.ndjson windows.

Activity

  1. juliusmarminge commented on Oct 16, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    This is a bug on current main: the desktop CDP relay serializes every preview event in the Electron main process with no size limit, so one oversized event can take down the whole app.

    What main does (apps/desktop/src/preview/CdpRelay.ts, unchanged since nightly 3171 apart from comment cleanup in #19559):

    • DesktopBrowserHost.attach registers a debugger.on("message") listener that passes every event to relay.event.
    • relay.event calls send = (message) => write(JSON.stringify(message)) once per attached page session, for every event, with no size check.
    • The relay enables no CDP domains itself. It forwards everything the server-side Playwright enables (Page, Runtime, Network and so on), so large Runtime/Network payloads from a big Vite dev page all go through this path.

    The stack shows JSON.stringify called from JS inside Debugger::Emit, so this is our listener, not an Electron bug. It's also a different failure from #8332 (SIGTRAP in debugger.attach) and #9067 (use-after-free, fixed by #9068).

    Which event is oversized is still a guess. If you can share the desktop.trace.ndjson window right before either crash, that would help pin it down.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 16, 2026
  3. Maxymvs commented on Oct 16, 2026

    @Maxymvs
    Author

    Thanks for confirming. I pulled the desktop.trace.ndjson windows, but they don't identify the event:

    • The trace has no spans for the CDP relay or individual debugger events, so none of the relayed traffic shows up. The only preview spans are PreviewManager.syncWebContentsState, registerWebview, publishFavicon and emitAgentPointer. All succeed in under 1 ms.
    • Nothing is slow or failing before either crash. The last spans are routine IPC and update polling, 3 to 4 seconds before each abort. That fits a single event-listener call that never returns, rather than load building up over time.
    • server.trace.ndjson has already rotated past Oct 15, so I can't send the Playwright side for these two crashes.

    Preview-related spans, local time (-0700). IPC and polling spans are omitted:

    Crash 1 (abort at 15:39:58)
    15:39:11.860 PreviewManager.registerWebview          0.3 ms  Success
    15:39:12.830 PreviewManager.publishFavicon           0.2 ms  Success
    15:39:16.936 PreviewManager.emitAgentPointer         0.2 ms  Success
    15:39:17.097 PreviewManager.emitAgentPointer         0.2 ms  Success
    15:39:44.757 PreviewManager.syncWebContentsState x15 <0.3 ms Success   (through 15:39:44.947)
    15:39:48.340 desktop.updates.checkForUpdates       146.3 ms  Success
    15:39:54.673 last span in the file before the abort (IPC)
    
    Crash 2 (relaunch at 15:40:20, abort at 15:40:41)
    15:40:28.828 PreviewManager.getBrowserSession        1.6 ms  Success
    15:40:29.698 PreviewManager.registerWebview          0.2 ms  Success
    15:40:30.205 PreviewManager.publishFavicon           0.3 ms  Success
    15:40:30.312 PreviewManager.syncWebContentsState x9  <0.3 ms Success   (through 15:40:30.789)
    15:40:35.940 desktop.updates.checkForUpdates       773.7 ms  Success
    15:40:37.938 last span in the file before the abort (IPC)
    

    To find the event, the relay would need to record something before calling JSON.stringify, such as the CDP method name and a rough size of params. Logging after the call doesn't help because the abort happens inside it. If a nightly adds that logging, I can run the same preview_open + preview_evaluate sequence against the page again and report the result. It reproduced within about 20 seconds of launch.

  4. kestereverts commented on Oct 20, 2026

    @kestereverts

    Still happening on a newer build: 3 main-process crashes in about 5 minutes on Nightly 0.1.1-nightly.20261020.3411 (d36b7da2), 20 October 2026. The machine and Electron binary are the same as in this report.

    Environment

    • T3 Code Nightly 0.1.1-nightly.20261020.3411, desktop app. This Mac hosts the server, and the user connects from another device, so each crash also takes down the bundled server and every remote thread.
    • Electron Framework 44.4.5, UUID 4C4C44ED-5555-3144-A1CB-427AA45E6F33. Same binary as this issue and [Bug]: macOS main-process crash in accessibility line-range handling (SIGTRAP) #17476.
    • macOS 27.0.1 (26A434), Apple silicon, 32 GB RAM. No memory pressure: swap 0, 68% free, renderer working sets about 300–400 MB.
    • Workload: 5 Claude and Codex threads running long builds in parallel, with agents driving preview tabs through the browser MCP tools (mcp: tab IDs).
    • The user had no crashes on 0.1.1-nightly.20261019.3360. The app updated to 3411 between 14:21 and 14:42 local time, and the first crash came at 15:07. Nothing between 3360 and 3411 touches CdpRelay.ts, so the update may not be the cause. The user's heavy browser use today could also explain the timing.

    Crashes

    All three .ips reports show EXC_BREAKPOINT (SIGTRAP) on thread 0 CrBrowserMain, brk #0 at the PC, and the same leading native frames at the same offsets. In crashes 2 and 3, six JIT frames sit above v8::Function::Call. That is the same shape as the stack in this issue. I have not symbolicated them.

    Timeline (UTC). Only the preview automation calls are shown:

    Crash 1 Crash 2 Crash 3
    Session start 12:40:04 13:07:41 13:10:24
    Preview tab created / webview registered 13:06:47 13:08:31 13:11:45
    Preview calls that timed out beforehand snapshot 13:04:16, snapshot 13:05:50, evaluate 13:06:20 snapshot 13:08:42, snapshot 13:09:05 snapshot 13:12:11
    Last desktop trace span 13:06:47.88 13:10:03.50 13:12:19.87
    Crash (.ips capture time) 13:07:08 13:10:08 13:12:26

    The pattern held in all three: the agent opens a tab, snapshot/evaluate time out (PreviewAutomationTimeoutError, 15000 ms), the desktop trace goes quiet for 5–20 s, and the main process aborts. The relaunched session ran agent threads but opened no preview tab, and it has not crashed so far.

    No Crashpad minidumps were written, and the unified log has no V8 fatal message. The .ips files are the only native evidence. I can share them.

    Related: #16567, which covers the snapshot timeouts that come before each crash.


    Filed via t3 triage by Claude Code (Claude Opus 5.5) on behalf of @kestereverts, with their approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions