Repository navigation
[Bug]: CLI aborts with "Assertion failed: (env) != nullptr" in node::RemoveEnvironmentCleanupHook (bundled Node 24.19.0 + better-sqlite3 12.11.1) #371
Description
Product or interface
CLI - interactive TUI
Version
- mcode 0.5.5 (
~/.minimax-code/current), installed with the official installer - bundled runtime: Node.js v24.19.0 (ABI 137) at
~/.minimax-code/runtime/node-v24.19.0-linux-x64 better-sqlite312.11.1 (optionalDependency, compiled locally against the bundled 24.19.0 headers)
Platform
Linux
OS version and architecture
Ubuntu 24.04.5 LTS x86_64, kernel 6.8.0-142-lowlatency, glibc 2.39
Issue area
Startup / install / update
Steps to reproduce
- Install MCode with the official installer (bundled Node 24.19.0 + better-sqlite3 12.11.1), no other changes.
- Run
mcode. - Let a session start, or exit the TUI.
This is a native GC/teardown race, so it is intermittent: reproduced once during Starting server..., and it is the same signature that other tools hit on shutdown.
Expected and actual behavior
Expected: the TUI starts and exits normally.
Actual: the process aborts with SIGABRT and prints a native stack trace ending in an assertion inside better-sqlite3's Statement destructor. The session data itself looks fine; the process just dies on teardown.
Redacted error summary
# minimax-code[125849]: void node::RemoveEnvironmentCleanupHook(v8::Isolate*, CleanupHook, void*) at ../src/api/hooks.cc:142
# Assertion failed: (env) != nullptr
----- Native stack trace -----
1: node::Assert(node::AssertionInfo const&)
2: node::RemoveEnvironmentCleanupHook(v8::Isolate*, void (*)(void*), void*)
3: Statement::~Statement() [/home/zsx/.minimax-code/releases/0.5.5/lib/node_modules/@minimax-ai/code/node_modules/better-sqlite3/build/Release/better_sqlite3.node]
4: Statement::~Statement() [better_sqlite3.node]
5: ... (GC / environment teardown path in the mcode binary)Analysis
This is not mcode logic; it is the interaction of the bundled Node 24.19.0 with the better-sqlite3 12.x native addon:
-
Node 24.19.0 added cleanup hooks to
node::ObjectWrap(src: add cleanup hooks tonode::ObjectWrapnodejs/node#63642, commit4b5eb7b72d). The header shipped inside the bundled runtime confirms it,include/node/node_object_wrap.h:ObjectWrap() { refs_ = 0; AddCleanupHook(); } virtual ~ObjectWrap() { RemoveCleanupHook(); ... } void RemoveCleanupHook() { RemoveEnvironmentCleanupHook(v8::Isolate::GetCurrent(), CleanupHook, this); }
-
If a wrapped object survives until the final GC after the environment has been torn down, its destructor calls
RemoveEnvironmentCleanupHook()with a destroyed environment,Environment::GetCurrent()returnsnullptr, and the(env) != nullptrassertion fires. This is Use-after-free inCleanupHookThunkRunfor everynode::ObjectWrapalive at teardown nodejs/node#65195 ("Use-after-free inCleanupHookThunkRunfor everynode::ObjectWrapalive at teardown"); the fix PR src: fix use-after-free in CleanupHookThunkRun nodejs/node#65196 is still open, and Node 24.20.0 / 24.21.0 do not contain a fix. -
better-sqlite312.11.1 still usesnode::ObjectWrap, which is compiled into the shipped addon. Verified on the installed binary:$ nm -D -C build/Release/better_sqlite3.node | grep -iE 'cleanup|ObjectWrap' W node::ObjectWrap::CleanupHook(void*) U node::AddEnvironmentCleanupHook(v8::Isolate*, void (*)(void*), void*) U node::RemoveEnvironmentCleanupHook(v8::Isolate*, void (*)(void*), void*)(
node-pty1.x and othernode::ObjectWrap-based addons are affected by the same Node regression.)
Suggested fix
Upgrade better-sqlite3 from 12.11.1 to 13.x, the first N-API release (node-addon-api), which does not use node::ObjectWrap:
$ nm -D -C prebuilds/linux-x64.node | grep -i cleanup
U napi_add_env_cleanup_hook
Alternatively, ship a Node runtime that contains the nodejs/node#65195 fix / #63642 revert. As a data point, several downstream projects fixed the same abort by moving to better-sqlite3 13.x.
Workaround verified locally
Replacing the bundled package in place (13.0.3, N-API prebuilt, no compile step) removes the abort:
cd ~/.minimax-code/releases/0.5.5/lib/node_modules/@minimax-ai/code/node_modules
mv better-sqlite3 better-sqlite3.orig
cp -a <better-sqlite3@13.0.3>/node_modules/better-sqlite3 better-sqlite3After that the TUI starts and exits cleanly (Starting server... -> Ask Mcode -> Intelligence with everyone, bye~) with no assertion, and the APIs mcode uses (prepare/exec/transaction/backup/pragma/function/aggregate/iterate/readonly/fileMustExist/close) all behave the same.
Note: MAVIS_SQLITE3_MODULE_PATH / database.sqlite3ModulePath does not work around this, because chunks/chunk-S2IS2DS4.js does a static import M8 from "better-sqlite3" that always resolves against the package's own node_modules; the bundled copy has to be replaced.
Before submitting
- I have searched existing issues.
- I have included my version and removed sensitive information.
Activity
hetaoBackend commentedon Sep 28, 2026
Excellent report — the native stack trace, the
nmsymbol evidence, and the traced Node upstream issue (nodejs/node#65195, cleanup-hook use-after-free at teardown) are exactly the kind of detail that makes a bug actionable. I verified the repo side and your root cause is confirmed, and there's already a fix in flight you can track.Verification (against current
main)better-sqlite3is pinned to 12.11.1 in both runtime packages:packages/local-runtime-v2/package.json:105andpackages/local-runtime/package.json:30. That's thenode::ObjectWrap-based major your analysis points at.node-addon-api) release, which is exactly thenapi_add_env_cleanup_hook(noObjectWrap) path you verified removes the abort. So the bump you're asking for is already queued.bug,cli) tracks theprebuild-install@7.1.3deprecation warning, which is also transitive viabetter-sqlite3@12.11.1— the same dependency, so the 13.x bump likely resolves that too.Classification
bug(a shipped native-add-on/Node interaction that aborts the process on teardown)needs-triagepending that dependency bump landing and a release picking it up. (This tracker has noready-for-humanlabel, soneeds-triage+ this note is the routing.)What happens next
ObjectWrapcleanup hooks.A couple of notes for the record
MAVIS_SQLITE3_MODULE_PATH/database.sqlite3ModulePathcan't work around this is correct and important:chunks/chunk-*.jsdoes a staticimport ... from "better-sqlite3", so the bundled copy under the package's ownnode_modulesalways wins. Good catch — it means the dependency bump is the only real fix, not a config shim.U node::AddEnvironmentCleanupHook/RemoveEnvironmentCleanupHookin 12.11.1 vsU napi_add_env_cleanup_hookin the 13.x prebuild) is precisely the evidence needed to justify the major bump. Referencing it on build(deps): bump better-sqlite3 from 12.11.1 to 13.0.3 #146 would help it get reviewed quickly if you have anything to add there.Thanks for the depth here — this was a genuinely well-diagnosed native crash. Not closing; linking to #146 for the fix.
No credentials or sensitive data echoed; the stack trace is from your own local run.