The Node 26 issue is understood and the temporary vscope workaround is implemented. I’d include this in today’s patch release.
Root cause
The first bad version is exactly Node 26.4.0:
@serialport/bindings-cpp invokes JavaScript from custom uv_async_t/uv_poll_t callbacks using FunctionReference::Call, which maps to napi_call_function. For custom asynchronous operations, Node documents that addons should establish an async context and use napi_make_callback; that also drains queued promises and process.nextTick work. Node-API documentation
Node’s optimization removed an incidental callback-scope drain that serialport was unknowingly relying on. Therefore:
- The Node change exposed the problem.
- The proper upstream fix belongs primarily in
@serialport/bindings-cpp.
- This explains why 0.1.1 worked: it was the Node runtime change, not vscope’s protocol or firmware changes.
Platform scope
This is not Windows-only:
So the defensible conclusion is “Windows and Linux confirmed; macOS currently unconfirmed/not reproduced,” rather than either Windows-only or universally all platforms.
Release workaround
client.ts now starts one 16 ms timer only on Node 26.4+:
- It is
unref()’d, so it cannot keep vscope alive.
- It covers daemon startup and all serial chunks.
- Node 24/25 and Node 26.0–26.3 remain untouched.
- Cost: 62.5 lightweight callbacks per second on affected Node versions. That is acceptable as a clearly temporary workaround.
A request-scoped timer was initially considered, but it did not cover the daemon-startup stall. Moving the response timeout also proved unsuitable: split serial frames could take roughly 470 ms and narrowly race the 500 ms timeout.
I also fixed the Windows release gate in package.json by removing the hard-coded /dev/null redirects.
Validation
Under Node 26.5, with COM4 at 312500 baud, 8N1, DTR/RTS enabled:
- Packaged daemon started without a preload.
- Health and MCP initialization passed.
- COM4 discovery and device connection passed.
- Device identified as
GIC0_LLC1.
- Run → trigger → halted/valid capture passed.
- Downloaded and persisted 2,000 samples × 5 channels.
- HTTP sample readback returned 40,000 bytes in the expected format.
- Smoke snapshot was deleted afterward.
- Full
pnpm run check exited successfully: lint, diagnostics, typechecks, 107 tests, runtime/UI builds, release assets, formatting and CLI smoke checks.
pnpm pack --dry-run passed.
Mise is restored to Node 26.5, and node resolves only through the mise shim. No vscope server was left running.
For upstream, I’d add the bisection and Node commit to issue #3148, then propose changing the raw Windows EIO_AfterRead/EIO_AfterWrite and POSIX Poller::onData callbacks to use Napi::AsyncContext plus MakeCallback. The two local modifications are uncommitted and ready for your patch.
The Node 26 issue is understood and the temporary vscope workaround is implemented. I’d include this in today’s patch release.
Root cause
The first bad version is exactly Node 26.4.0:
2a672ee9, “fast path empty native immediate drain”.@serialport/bindings-cppinvokes JavaScript from customuv_async_t/uv_poll_tcallbacks usingFunctionReference::Call, which maps tonapi_call_function. For custom asynchronous operations, Node documents that addons should establish an async context and usenapi_make_callback; that also drains queued promises andprocess.nextTickwork. Node-API documentationNode’s optimization removed an incidental callback-scope drain that serialport was unknowingly relying on. Therefore:
@serialport/bindings-cpp.Platform scope
This is not Windows-only:
So the defensible conclusion is “Windows and Linux confirmed; macOS currently unconfirmed/not reproduced,” rather than either Windows-only or universally all platforms.
Release workaround
client.ts now starts one 16 ms timer only on Node 26.4+:
unref()’d, so it cannot keep vscope alive.A request-scoped timer was initially considered, but it did not cover the daemon-startup stall. Moving the response timeout also proved unsuitable: split serial frames could take roughly 470 ms and narrowly race the 500 ms timeout.
I also fixed the Windows release gate in package.json by removing the hard-coded
/dev/nullredirects.Validation
Under Node 26.5, with COM4 at 312500 baud, 8N1, DTR/RTS enabled:
GIC0_LLC1.pnpm run checkexited successfully: lint, diagnostics, typechecks, 107 tests, runtime/UI builds, release assets, formatting and CLI smoke checks.pnpm pack --dry-runpassed.Mise is restored to Node 26.5, and
noderesolves only through the mise shim. No vscope server was left running.For upstream, I’d add the bisection and Node commit to issue #3148, then propose changing the raw Windows
EIO_AfterRead/EIO_AfterWriteand POSIXPoller::onDatacallbacks to useNapi::AsyncContextplusMakeCallback. The two local modifications are uncommitted and ready for your patch.