Skip to content

fix: require better-sqlite3 ^12.8.0 so Node 24 does not abort on statement GC - #29

Open
proffesor-for-testing wants to merge 1 commit into
ruvnet:mainfrom
proffesor-for-testing:fix/better-sqlite3-12-node24
Open

proffesor-for-testing wants to merge 1 commit into
ruvnet:mainfrom
proffesor-for-testing:fix/better-sqlite3-12-node24

Conversation

@proffesor-for-testing

@proffesor-for-testing proffesor-for-testing commented Oct 9, 2026 •

Copy link
Copy Markdown

Problem

On Node 24.19 and later, processes using agentdb's native SQLite path abort intermittently:

#  node[...]: void node::RemoveEnvironmentCleanupHook(v8::Isolate*, CleanupHook, void*) at ../src/api/hooks.cc:142
#  Assertion failed: (env) != nullptr
 3: node::RemoveEnvironmentCleanupHook(...) [node]
 4: Statement::~Statement() [.../agentdb/node_modules/better-sqlite3/build/Release/better_sqlite3.node]

In ruflo this kills ruflo memory search (exit 134) and the ruflo MCP server ("Connection closed" on memory_search_unified, memory_store and agentdb_pattern-search). See ruvnet/ruflo#3955.

Root cause

  • Node bug. Node 24.19.0 backported "add cleanup hooks to node::ObjectWrap" without the cleanup-hook registry that makes it safe (24.19.0: node::ObjectWrap cleanup hooks backported without the cleanup hook registry, aborts on every 24.x runtime nodejs/node#65446; the backport PR [v24.x backport] src: keep global list of addon-provided cleanup hooks nodejs/node#65943 was closed without merging). node_object_wrap.h is header-only, so any addon compiled against 24.19+ headers calls RemoveEnvironmentCleanupHook() from ~ObjectWrap(). When V8 collects a wrapped object in an allocation-driven GC, there is no current Environment and the process aborts. better-sqlite3 has the same report: Intermittent ObjectWrap cleanup abort during Statement GC on Node 24 WiseLibs/better-sqlite3#1515.
  • Why ^11 is exposed. agentdb declares better-sqlite3: ^11.8.1, which resolves to 11.10.0. The 11.10.0 release has no node-v137 (Node 24) prebuild; its Linux prebuilds stop at node-v131. So on Node 24, prebuild-install falls back to node-gyp rebuild against the local headers:
    $ ls node_modules/better-sqlite3/build/Release/            # 11.10.0 on Node 24.21.0
    better_sqlite3.node  obj  obj.target  sqlite3.a  ...         # built from source
    $ nm -D --undefined-only .../better_sqlite3.node | grep CleanupHook
                     U _ZN4node25AddEnvironmentCleanupHook...
                     U _ZN4node28RemoveEnvironmentCleanupHook...  # the regressed call
    
  • Why ^12 is safe. better-sqlite3 12.11.1 ships a node-v137 prebuild built on 2026-06-15, before the regression. It does not import RemoveEnvironmentCleanupHook.
  • Consumers can't work around it. ruflo's own overrides cannot reach this: npm ignores overrides in a dependency's package.json. Even npm i -g ruflo installs the nested 11.10.0 under agentdb/. So the range has to change here.

Fix

  • optionalDependencies.better-sqlite3: ^11.8.1 → ^12.8.0. package-lock.json now resolves 12.11.1, the last 12.x.
  • The calls agentdb makes are unchanged in 12.x: new Database, pragma, prepare/run/get/all, exec, transaction, close.
  • New regression test tests/better-sqlite3-statement-gc.test.ts:
  • CHANGELOG entry under [Unreleased].

Verification (linux-x64)

Minimal probe, 200k prepared statements dropped under allocation pressure, Node 24.21.0:

11.10.0 (built from source)  ok=0/5  aborts=5
12.11.1 (prebuild)           ok=5/5  aborts=0

New test, Node 24.21.0:

with 12.11.1:                     Tests 2 passed (2)
with node_modules/better-sqlite3 swapped for a source-built 11.10.0:
  × survives statements collected by allocation-driven GC   (Tests 1 failed | 1 passed)

Existing suites. I ran the 29 test files that touch better-sqlite3, db-fallback or new AgentDB. The full vitest --run (it includes simulation/) did not finish within 30 minutes here.

Run Test files Tests Worker aborts (env != nullptr)
Node 24.21 + 11.10.0 (main) 9 failed / 10 passed 82 failed / 268 passed / 26 skipped 10
Node 24.21 + 12.11.1 (this PR) 14 failed / 15 passed 150 failed / 443 passed / 26 skipped 0
Node 22.23 + 11.10.0 (main, prebuild) 13 failed / 16 passed 148 failed / 445 passed / 26 skipped 0
Node 22.23 + 12.11.1 (this PR) 14 failed / 15 passed 149 failed / 444 passed / 26 skipped 0

How to read the table:

  • On Node 24, main loses 10 worker processes to this abort. Those files never report, which is why main shows fewer failures than this branch.
  • The failures that remain with 12.x already fail on main under Node 22 with 11.x. Examples: Unknown controller: self-attention, memoryController.store is not a function, Embedding outputDimension must be a positive integer, and Cannot find module for files that need a build.
  • Two tests differ between the runs, and neither is caused by this change:
    • vector-search > should benefit from embedding cache is a timing comparison. It also fails 2/3 runs on main with Node 22 and 11.x.
    • cli-mcp-integration > should work with SkillLibrary on SQLite passes 3/3 when its file runs alone on 12.x. It only fails when it shares the run with other files (UNIQUE constraint failed: skills.name).

End-to-end with ruflo. I ran npm pack on this branch after npm run build, then npm install ruflo@3.56.2 ./agentdb-3.0.0-alpha.20.tgz in a scratch project. This is unmodified ruflo, without ruvnet/ruflo#3955.

12.11.1 node_modules/better-sqlite3                      # agentdb now dedupes onto it
11.10.0 node_modules/agentic-flow/node_modules/better-sqlite3   # via agentdb@1.6.1, not loaded on this path

Node 24.21.0, `ruflo memory search -q "agentic-qe ruflo plugin" --smart -n patterns`
on a .swarm/ fixture that aborted 5/10 runs with ruflo 3.56.1:
10 runs  nonzero=0  aborts=0
loaded better_sqlite3.node: node_modules/better-sqlite3/build/Release/better_sqlite3.node (only)

Notes / not covered

  • Node versions. better-sqlite3 12 declares engines.node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x. agentdb still says >=18. better-sqlite3 12.10.0 also dropped the Node 20 prebuilds, so 12.11.1 ships prebuilds only for node-v127/137/141/147 (11.10.0 had v108/115/127/131). On Node 18 and Node 20 (both EOL), npm builds 12.x from source; it is optional, so agentdb falls back to sql.js if that fails (verified: with better-sqlite3 absent, getDatabaseImplementation() uses sql.js). Raising agentdb's engines is left to the maintainers.
  • Source builds still crash. Any better-sqlite3 built from source against Node ≥24.19 headers will still abort. That covers platforms without a matching prebuild (musl x64 does have one: node-v137-linuxmusl-x64 ships in 12.8.0–12.11.1), --build-from-source and npm rebuild. The real fix is 24.19.0: node::ObjectWrap cleanup hooks backported without the cleanup hook registry, aborts on every 24.x runtime nodejs/node#65446.
  • Keep the 12.x range for now. The 12.x line ended at 12.11.1, so ^12.8.0 keeps resolving to the pre-regression prebuild. Moving to 13.x needs this checked again.
  • Companion ruflo PR. fix(memory): open AgentDB with our better-sqlite3 so Node 24 does not abort ruflo#3955 makes ruflo open AgentDB with its own better-sqlite3 for releases that still carry agentdb ^11. This PR fixes it for every consumer.

🤖 Generated by Ruflo & AQE

…ement GC

better-sqlite3 11.x publishes no Node 24 (ABI 137) prebuild, so `^11.8.1`
was compiled locally against the running Node's headers. Node 24.19+
headers carry a partial node::ObjectWrap cleanup-hook backport
(nodejs/node#65446); the locally built addon then aborts with
`Assertion failed: (env) != nullptr` when V8 collects a Statement in an
allocation-driven GC. ruflo hits this mid memory search (exit 134,
"Connection closed" over MCP).

better-sqlite3 12.x ships a Node 24 prebuild built before the regression.
Its API is unchanged for the calls agentdb makes; it requires Node >= 20.

The new test prepares and drops 200k statements in a child process and
fails on a source-built 11.x under Node 24.21 (abort), passing on 12.x.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant