Skip to content

Server aborts on Node 24 when a better-sqlite3 built from source frees a statement #719

Description

@irparent

What happens

On Node 24, the Iris server can abort with a native assertion while it is serving:

node::RemoveEnvironmentCleanupHook(...) at ../src/api/hooks.cc:142
Assertion failed: (env) != nullptr
 3: node::RemoveEnvironmentCleanupHook(...)
 4: Statement::~Statement() [.../better-sqlite3/build/Release/better_sqlite3.node]

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.

Cause

Measured (Node 24.21.0 container, better-sqlite3 12.11.1)

Binary Statements freed with the db open Freed after db.close() 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.

Activity

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

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