Skip to content

fix: prevent EXC_BREAKPOINT crash from V8 string allocation in stream…#7509

Closed
AE-33 wants to merge 1 commit intoblock:mainfrom
AE-33:fix/electron-stream-crash-exc-breakpoint
Closed

fix: prevent EXC_BREAKPOINT crash from V8 string allocation in stream…#7509
AE-33 wants to merge 1 commit intoblock:mainfrom
AE-33:fix/electron-stream-crash-exc-breakpoint

Conversation

@AE-33
Copy link
Copy Markdown

@AE-33 AE-33 commented Feb 25, 2026

Summary

Fixes EXC_BREAKPOINT (SIGTRAP) crash on macOS ARM64 that occurs ~29 minutes after launch.

Root cause: Buffer.toString() called inside Node.js stream 'data' event handlers on the Electron main thread triggers node::StringBytes::Encode()v8::String::NewFromUtf8() → V8 CHECK assertion failure (brk #0) when V8 cannot allocate under GC pressure.

Fixes applied to 3 vulnerable code paths:

  1. goosed.ts stdout/stderr: Batch raw Buffer chunks, flush via setImmediate() — moves toString() out of the libuv I/O callback into the next event loop tick
  2. main.ts check-ollama: Accumulate Buffer[] during 'data' events, convert to string on 'close' after stream ends
  3. main.ts read-file: Replace spawn('cat') + stream piping with fs.readFile() on all platforms — eliminates per-chunk V8 string allocation entirely

Type of Change

  • Bug fix

AI Assistance

  • This PR was created or reviewed with AI assistance

Testing

  • Manual analysis of crash report and V8 source code to confirm root cause
  • Verified syntax correctness of all changes
  • read-file handler returns identical API shape (backward compatible)
  • check-ollama handler returns identical results (just defers string conversion)
  • goosed.ts logging output is identical (just batched per event loop tick)

Related Issues

Closes #7507

@AE-33 AE-33 force-pushed the fix/electron-stream-crash-exc-breakpoint branch from c9b4106 to 802810b Compare February 25, 2026 16:41
@zanesq zanesq self-assigned this Feb 25, 2026
@zanesq
Copy link
Copy Markdown
Contributor

zanesq commented Feb 25, 2026

@AE-33 thanks! I've been looking into this also and have some other fixes in progress so I will merge some of this into my branch #7488 and test it.

@AE-33
Copy link
Copy Markdown
Author

AE-33 commented Feb 25, 2026

@AE-33 thanks! I've been looking into this also and have some other fixes in progress so I will merge some of this into my branch #7488 and test it.

Love it! sounds like a plan to me @zanesq 🙌

zanesq pushed a commit that referenced this pull request Feb 25, 2026
…n in check-ollama

Adapted from PR #7509 — eliminates per-chunk Buffer.toString() calls in
stream data handlers that contribute to V8 heap pressure in long sessions.

- read-file: use fs.readFile() on all platforms instead of spawning cat
- check-ollama: accumulate chunks, convert to string on stream close
… handlers

The Goose desktop app crashes with EXC_BREAKPOINT (SIGTRAP) / brk #0 on the
main thread (CrBrowserMain) approximately 29 minutes after launch.

Root cause: calling Buffer.toString() inside Node.js stream 'data' event
handlers on the Electron main thread triggers the following V8 code path:

  Buffer.toString('utf8')
  → node::StringBytes::Encode()
  → v8::String::NewFromUtf8()
  → V8 NEW_STRING macro → .ToHandleChecked()

When V8 cannot allocate a new string (GC pressure, near-OOM, or isolate
state during concurrent garbage collection), NewFromUtf8 returns an empty
MaybeLocal, and ToHandleChecked() hits a CHECK → FATAL → brk #0 →
EXC_BREAKPOINT (SIGTRAP).

This is triggered because libuv delivers stream data chunks via callbacks
on the main thread's event loop. Each data.toString() call forces an
immediate V8 heap allocation in the hot I/O path, competing with GC.

Fixes applied to three vulnerable code paths:

1. check-ollama handler (main.ts): Accumulate raw Buffer chunks in arrays
   during 'data' events, defer Buffer.concat().toString() to the 'close'
   handler after the stream has ended.

2. read-file handler (main.ts): Replace spawn('cat') + stream piping with
   fs.readFile() on all platforms. This returns a single Buffer after the
   read completes, avoiding per-chunk V8 string allocation entirely.

3. goosed process stdout/stderr (goosed.ts): Batch raw Buffer chunks and
   flush via setImmediate(), moving the toString() call out of the libuv
   I/O callback and into a subsequent event loop tick where V8 heap
   allocation is safer.

Crash signature from macOS crash report:
  Exception Type:  EXC_BREAKPOINT (SIGTRAP)
  Exception Codes: 0x0000000000000001, 0x0000000112b6cef0
  Thread 0 Crashed: CrBrowserMain
  0 Electron Framework  ares_dns_rr_get_ttl + 3711128
  ...
  29 node::StringBytes::Encode + 272
  30 node::Buffer::RegisterExternalReferences + 25908

Electron 40.4.0 / Node.js 24.x / V8 14.4.x / macOS ARM64

Signed-off-by: Jacob Szymik <[email protected]>
@AE-33 AE-33 force-pushed the fix/electron-stream-crash-exc-breakpoint branch from 802810b to 61cdf01 Compare February 25, 2026 22:15
@zanesq
Copy link
Copy Markdown
Contributor

zanesq commented Feb 26, 2026

Closing in favor of #7488 @AE-33 can you do the dco signoff on that PR?

@zanesq zanesq closed this Feb 26, 2026
@zanesq
Copy link
Copy Markdown
Contributor

zanesq commented Feb 26, 2026

@AE-33 nvm it was a different contributor, cleaning that up in the branch

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EXC_BREAKPOINT (SIGTRAP) crash in main process — V8 string allocation failure in stream data handlers

2 participants