Skip to content

bug(pty): uncaught Napi::Error from conpty.node aborts the main process — 12 crashes, one signature (0.10 → 0.41) #150

Description

@Ray0483

Environment

  • wmux: crashes observed on 0.10.0, 0.15.0, 0.17.0, 0.20.0, 0.25.0, 0.27.2, 0.29.1, 0.30.0, 0.38.0, 0.39.1, 0.41.0 (currently on 0.46.0)
  • Windows 11 Pro 26200 (10.0.26200.2.0.0.256.48), x64
  • Chromium/ANGLE in this build: 2.1.27949 git hash: bbf3d8a47552
  • Bundled node-pty: 1.1.0 (resources/app.asar.unpacked/node_modules/node-pty), ConPTY backend

Symptom

wmux disappears instantly — the entire window, every workspace, every pane, with no error dialog and nothing written to the sidebar. It reads like a process kill, not a UI fault. Roughly once every 2–4 days under normal use.

Windows records it as Application Error / BEX64:

Faulting application name: wmux.exe, version: 0.41.0.0, time stamp: 0x6a3b18dc
Faulting module name: wmux.exe
Exception code: 0xc0000409          (STATUS_STACK_BUFFER_OVERRUN)
Fault offset:   0x0000000003290be5
Additional parameter: 0x7           (FAST_FAIL_FATAL_APP_EXIT)

0xc0000409 here is not memory corruption — parameter 7 means the process deliberately called __fastfail(FAST_FAIL_FATAL_APP_EXIT), i.e. the CRT abort() path.

Evidence: 12 crashes, one signature

The Windows Event Log records 12 crashes between 2026-06-27 and 2026-08-04. I have minidumps from %LOCALAPPDATA%\CrashDumps for the 8 most recent, and every one is identical:

Crash time (local) wmux version Exception Param Fault offset Crashing thread
2026-07-19 15:40:37 0.25.0 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)
2026-07-22 14:04:18 0.27.2 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)
2026-07-22 14:05:48 0.29.1 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)
2026-07-25 11:10:17 0.30.0 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)
2026-07-29 19:41:32 0.30.0 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)
2026-07-30 10:20:32 0.38.0 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)
2026-08-02 12:44:53 0.39.1 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)
2026-08-04 11:52:55 0.41.0 0xc0000409 0x7 wmux.exe+0x3290be5 index 0 (main)

The four older Event Log entries (0.10.0, 0.15.0, 0.17.0, 0.20.0) carry the same exception code and parameter; the two oldest sit at a different offset (+0x45e3515), which is consistent with the same bug before an Electron bump.

Note the two crashes 90 seconds apart on 07-22: restarting wmux hit the same path almost immediately.

Root cause: an unhandled C++ Napi::Error thrown inside conpty.node

The faulting stack carries both the raise path and the abort path, which means the exception was never handled:

KERNELBASE!RaiseException+0x8a                  <- C++ throw
ntdll!RtlVirtualUnwind / ntdll!__C_specific_handler
KERNELBASE!UnhandledExceptionFilter+0x1f3       <- nobody caught it
wmux.exe+0x3294e5a
wmux.exe+0x32abfd2                              <- abort()
wmux.exe+0x3290be5                              <- __fastfail(7)  *** crash ***

The stack also contains 0xE06D7363 (MSVC C++ EH exception code, "msc") and 0x19930520 (C++ EH magic), so it is a C++ throw, not an SEH fault.

Decoding the EXCEPTION_RECORD gives a well-formed C++ throw record (NumberParameters=4, ExceptionInformation[0] == 0x19930520). Walking _ThrowInfo -> _CatchableTypeArray -> TypeDescriptor yields the thrown type:

.?AVError@Napi@@                            <- Napi::Error
.?AVObjectReference@Napi@@
.?AV?$Reference@VObject@Napi@@@Napi@@
.?AVexception@std@@

And critically, ExceptionInformation[3] (the throw's image base) equals the load address of
resources\app.asar.unpacked\node_modules\node-pty\prebuilds\win32-x64\conpty.node
in every dump — the _ThrowInfo lives in that module's .rdata, so the throw statement is compiled into conpty.node.

So: conpty.node throws Napi::Error in a context with no handler. The C++ exception escapes into the main thread's message loop, the CRT unhandled-exception path calls abort(), and the Electron browser process dies — taking every pane with it.

Because a Napi::Error thrown at a normal N-API method boundary is caught by node-addon-api's wrapper and converted into a JS throw, the fact that it reached UnhandledExceptionFilter means the throw happened outside such a boundary.

Where it likely comes from (inferred, not proven)

conpty.node contains these strings:

SetupExitCallback_resource
ThreadSafeFunction.BlockingCall() failed
ThreadSafeFunction.Release() failed

node-pty's ConPTY path registers a process-exit callback via a ThreadSafeFunction (windowsPtyAgent.js:89):

const connect = this._ptyNative.connect(this._pty, commandLine, cwd, env, this._useConptyDll,
                                        c => this._$onProcessExit(c));

A Napi::Error thrown from a TSFN/libuv callback runs on the main thread with no node-addon-api try/catch above it — which matches all observed facts (main thread, shallow ~24 KB stack, no handler). That would make the trigger a pane's shell process exiting, which fits the irregular every-few-days cadence.

To be explicit: this last section is inference from the module's string table and the crashing thread's identity, not something I proved from the dump. Everything in the previous section is proven.

For completeness, the JS call sites into the native module are unguarded (windowsPtyAgent.js:123/130/150/156), but wrapping them in try/catch would not fix this — the exception never becomes a JS exception.

Suggested next steps

  1. You can symbolize this quickly: the crash is at wmux.exe+0x3290be5 in the 0.41.0 build (time stamp 0x6a3b18dc), and conpty.node ships with a PDB path of D:\a\_work\1\s\build\Release\conpty.pdb. I'm happy to upload the minidumps (~6 MB each) if that would help — just say where.
  2. A cheap hardening regardless of the exact throw site: build/ship conpty.node with NAPI_DISABLE_CPP_EXCEPTIONS, so N-API failures surface as JS errors instead of C++ throws that can abort the process.
  3. Related but distinct, already fixed: bug(pty): MSVC assertion crash on close — remove_pty_baton(baton->id) #50 (remove_pty_baton MSVC assertion on close). This one is a different mechanism — a 0xE06D7363 C++ throw, not an assertion — and still reproduced on 0.41.0.

How to capture better data

The current dumps are minidumps, so the Napi::Error message is unrecoverable: Napi::Error::_message is lazily computed and still empty at throw time, and the text lives in the V8 heap behind a napi_ref. Setting

HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\wmux.exe
  DumpType = 2   (full dump)

on an affected machine will capture the actual error message on the next occurrence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions