You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Server aborts on Node 24 when a better-sqlite3 built from source frees a statement #719
It happened once in CI, in test (24, node) of run 36453179190 (Node 24.21.0, ubuntu), in tests/integration/search-index-build-stdio.test.ts: the server died mid-session and the client saw MCP error -32000: Connection closed. A re-run passed.
The prebuilt 12.11.1 binaries were built on 2026-06-15, against older headers. They do not contain the call and are safe. In the failing job the prebuilt download did not happen: npm ci took 1m24s there against 4s in the other test jobs of the same run, because prebuild-install || node-gyp rebuild compiled the addon from source against Node 24.21.0's headers. A user whose prebuild download fails (a proxy, an offline mirror, a platform without a prebuild) gets the same binary.
process.exit, a natural end, SIGTERM→close→exit, uncaught throw
Compiled from source
aborted 5 of 5
aborted 5 of 5
0 of 10 each
Prebuilt
0 of 5
0 of 5
not run
So finalizing statements or closing the database does not prevent it. Only a garbage collection of a statement or database object triggers it. Frequency in CI: 1 of the 62 failed jobs in the last 400 CI runs (2026-09-21 to 2026-09-28). It is the only one of those jobs whose install compiled the addon.
better-sqlite3 13.x moved to N-API (Napi::ObjectWrap), which is not affected. It is not a safe upgrade yet, because #308 records spawned-process crashes with 13.0.3 in CI.
Fix
Before loading better-sqlite3, the driver seam reads the binary that require('better-sqlite3') would load. If the binary carries the RemoveEnvironmentCleanupHook import and the runtime lacks the global hook list (every 24.x release so far, and 26.x before 26.4.0), Iris holds the store with Node's built-in SQLite, warns once and gives the reason. With IRIS_SQLITE_DRIVER=native it refuses and names the fix: npm rebuild better-sqlite3 restores the prebuilt binary.
The end of stdin shuts a stdio server down in order (the store closed, an in-flight search-index build stopped), where it used to drain and exit with the build still running.
A CI job compiles better-sqlite3 from source on Node 24 (Linux, macOS, Windows) and Node 22 (Linux, macOS). It requires that the binary aborts exactly when Iris predicts it, that Iris never loads it on Node 24, and that the real server, started and stopped over stdio repeatedly, ends every session cleanly.
What happens
On Node 24, the Iris server can abort with a native assertion while it is serving:
It happened once in CI, in
test (24, node)of run 36453179190 (Node 24.21.0, ubuntu), intests/integration/search-index-build-stdio.test.ts: the server died mid-session and the client sawMCP error -32000: Connection closed. A re-run passed.Cause
node::ObjectWrap: its destructor now callsnode::RemoveEnvironmentCleanupHook(isolate, ...). The runtime side of that change, a global list of addon cleanup hooks, was not included, so during a garbage collection there is no current Environment and the call aborts. Upstream: 24.19.0:node::ObjectWrapcleanup hooks backported without the cleanup hook registry, aborts on every 24.x runtime nodejs/node#65446. The backport, [v24.x backport] src: keep global list of addon-provided cleanup hooks nodejs/node#65943, is onv24.x-stagingbut in no 24.x release up to 24.21.0.ObjectWrap. A binary compiled against 24.19+ headers aborts the first time V8 frees one of them. Also reported at Intermittent ObjectWrap cleanup abort during Statement GC on Node 24 WiseLibs/better-sqlite3#1515.npm citook 1m24s there against 4s in the other test jobs of the same run, becauseprebuild-install || node-gyp rebuildcompiled the addon from source against Node 24.21.0's headers. A user whose prebuild download fails (a proxy, an offline mirror, a platform without a prebuild) gets the same binary.Measured (Node 24.21.0 container, better-sqlite3 12.11.1)
db.close()process.exit, a natural end, SIGTERM→close→exit, uncaught throwSo finalizing statements or closing the database does not prevent it. Only a garbage collection of a statement or database object triggers it. Frequency in CI: 1 of the 62 failed jobs in the last 400 CI runs (2026-09-21 to 2026-09-28). It is the only one of those jobs whose install compiled the addon.
better-sqlite3 13.x moved to N-API (
Napi::ObjectWrap), which is not affected. It is not a safe upgrade yet, because #308 records spawned-process crashes with 13.0.3 in CI.Fix
require('better-sqlite3')would load. If the binary carries theRemoveEnvironmentCleanupHookimport and the runtime lacks the global hook list (every 24.x release so far, and 26.x before 26.4.0), Iris holds the store with Node's built-in SQLite, warns once and gives the reason. WithIRIS_SQLITE_DRIVER=nativeit refuses and names the fix:npm rebuild better-sqlite3restores the prebuilt binary.