Skip to content

Uncaught C++ exception from PtyFork crashes the whole app (SIGABRT, no JS error frame) #1873

Description

@pixexid

bb 0.38.0, macOS (Darwin 27). The desktop app died with SIGABRT mid-stream while a large thread was rendering. No JS error frame was produced, so from the agent side it looked like a silent native death — every live thread entered status=error at once.

The crash report gives a specific cause, and it is not silent at the native level.

Signature

exception:    EXC_CRASH / SIGABRT
termination:  Abort trap: 6 (SIGNAL)
ASI:          libsystem_c.dylib: "abort() called"

Faulting thread, reading down from the abort:

__pthread_kill
pthread_kill
__abort
abort
__abort_message
demangling_terminate_handler()
_objc_terminate()
std::__terminate(void (*)())
__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*)
__cxa_throw
pty.node          PtyFork(Napi::CallbackInfo const&)      <-- throws here
watcher.node      Napi::details::CallbackData<...>::Wrapper(napi_env__*, napi_callback_info__*)
Electron Framework  napi_is_detached_arraybuffer
...
Electron Framework  v8::Function::NewInstanceWithSideEffectType(...)

So: PtyFork throws a C++ exception, nothing catches it at the N-API boundary, it reaches std::terminate, and the process aborts. __cxa_throw sitting directly above failed_throw is the tell — the throw found no handler at all.

Why this reads as "silent"

The exception never becomes a JS error, so there is no stack in the renderer or main logs, no uncaughtException handler fires, and nothing is written before the abort. A consumer sees the process vanish. The only artifact is the OS crash report.

That matters beyond diagnosis: an agent fleet cannot distinguish this from a hang or a network partition without reading DiagnosticReports, which is not something a supervising process would think to do.

Likely trigger

PtyFork typically throws when the underlying fork/posix_spawn fails — EAGAIN from process-table or per-user process limits, or EMFILE/ENFILE from file-descriptor exhaustion.

The observed conditions fit: a fleet with dozens of concurrent threads, several git worktrees, and file watchers active. watcher.node appearing directly beneath the pty frame suggests both native addons were live in the same callback path. Under that pressure, a new PTY spawn failing is ordinary — but it should surface as a JS error on the spawn call, not take the app down.

The ask

Two separable things, and the first is worth doing regardless of the second:

  1. Catch at the N-API boundary. Wrap PtyFork's body so a C++ throw is converted into a JS exception (Napi::Error::New(env, ...).ThrowAsJavaScriptException()) rather than propagating into std::terminate. A failed PTY spawn is a recoverable, ordinary condition — resource limits are hit by long-running sessions in the normal course of things. It should reject the spawn, not abort the process.

  2. Consider whether the failure is avoidable. If this is fd or process exhaustion, whatever accumulates them is the deeper issue. But (1) is the correctness fix: even with the leak fixed, a spawn under load can fail, and that path must not be fatal.

Timeline, for correlation

  • died between 02:39 and 02:41 UTC
  • process back up 02:40:56
  • SIGTERM 02:41:53
  • stable from 02:42:24

Repro shape, though I have not reduced it: largest thread in the workspace, mid-stream, in a session predating the last restart, with many concurrent threads and worktrees live.

Recovery was clean once restarted — no data loss, and a plugin's canonical SQLite store came back intact — so this is a robustness issue rather than a corruption one.

Adjacent note

0.39.0 removed bb.sdk.threads.rateLimitRecovery from ThreadsArea with no in-surface replacement. That is filed separately, but it is worth mentioning together: a plugin declaring compatibility >=0.37.0 sees the surface shrink under it, and a crash plus a contract break in the same window is a rough upgrade story.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions