fix: detect and rebuild better-sqlite3 on Node.js ABI mismatch#1373
fix: detect and rebuild better-sqlite3 on Node.js ABI mismatch#1373zerone0x wants to merge 1 commit intoMemTensor:mainfrom
Conversation
The postinstall script only checked whether the native binding file existed on disk. After a Node.js version update, the .node binary can still be present but compiled against a different ABI, causing a MODULE_VERSION mismatch at load time. This adds a runtime load check so the script automatically triggers `npm rebuild better-sqlite3` when the existing binary is incompatible with the running Node.js. Closes MemTensor#1343 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates the plugin’s postinstall flow to detect better-sqlite3 native binding ABI mismatches (e.g., NODE_MODULE_VERSION changes after a Node.js update) and automatically rebuild the module when needed, preventing runtime load failures after plugin updates.
Changes:
- Adds a
sqliteLoadsSuccessfully()probe that attempts torequire()better-sqlite3 to catch ABI mismatch failures even when a.nodefile exists. - Adjusts Phase 2 logic to rebuild better-sqlite3 when bindings are missing or fail to load.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } else if (!sqliteLoadsSuccessfully()) { | ||
| log("Native binding file exists but cannot be loaded by current Node.js."); | ||
| needsRebuild = true; | ||
| } else { |
There was a problem hiding this comment.
Now that the script distinguishes “binding exists” vs “binding loads”, the post-rebuild success path should also validate loadability (not just file existence). Otherwise npm rebuild could succeed and produce a .node file that still fails to load (ABI mismatch persists, wrong prebuild selected, missing runtime dependency), and the script would report success anyway.
| let needsRebuild = false; | ||
|
|
||
| if (!sqliteBindingsExist()) { | ||
| warn("better-sqlite3 native bindings not found in plugin dir."); | ||
| log(`Searched in: ${DIM}${sqliteModulePath}/build/${RESET}`); | ||
| needsRebuild = true; | ||
| } else if (!sqliteLoadsSuccessfully()) { | ||
| log("Native binding file exists but cannot be loaded by current Node.js."); | ||
| needsRebuild = true; |
There was a problem hiding this comment.
needsRebuild is set in multiple branches but never read; as written the script rebuilds whenever it doesn't process.exit(0). Remove the variable or use it to explicitly gate the rebuild step to avoid dead code/confusion.
| let needsRebuild = false; | |
| if (!sqliteBindingsExist()) { | |
| warn("better-sqlite3 native bindings not found in plugin dir."); | |
| log(`Searched in: ${DIM}${sqliteModulePath}/build/${RESET}`); | |
| needsRebuild = true; | |
| } else if (!sqliteLoadsSuccessfully()) { | |
| log("Native binding file exists but cannot be loaded by current Node.js."); | |
| needsRebuild = true; | |
| if (!sqliteBindingsExist()) { | |
| warn("better-sqlite3 native bindings not found in plugin dir."); | |
| log(`Searched in: ${DIM}${sqliteModulePath}/build/${RESET}`); | |
| } else if (!sqliteLoadsSuccessfully()) { | |
| log("Native binding file exists but cannot be loaded by current Node.js."); |
| warn("ABI version mismatch — native module was built for a different Node.js version."); | ||
| return false; | ||
| } | ||
| // For any other load error fall through to the rebuild path as well. |
There was a problem hiding this comment.
sqliteLoadsSuccessfully() intentionally suppresses all non-ABI load errors. That makes failures like missing DLLs / wrong arch / missing dependency harder to diagnose. Consider logging (at least a truncated) e.message when the error is not a NODE_MODULE_VERSION mismatch so users have actionable context before the rebuild attempt.
| // For any other load error fall through to the rebuild path as well. | |
| // For any other load error, log a truncated message so users have context | |
| // before we fall through to the rebuild path. | |
| const rawMsg = e && e.message ? String(e.message) : String(e); | |
| const truncatedMsg = rawMsg.length > 300 ? rawMsg.slice(0, 297) + "..." : rawMsg; | |
| warn(`better-sqlite3 failed to load; will attempt to rebuild. Error: ${truncatedMsg}`); |
Summary
NODE_MODULE_VERSIONmismatch that preventsmemos-local-openclaw-pluginfrom loading after a Node.js or plugin update on Windows (and other platforms)postinstall.cjsscript previously only checked whether the.nodebinary file existed on disk; it now also attempts torequire()the module and catches ABI mismatch errorsnpm rebuild better-sqlite3runs automatically — no manual intervention neededDetails
The root cause:
openclaw plugins updateinstalls new JS files but may reuse the existing native binary built for an older Node.js ABI. The postinstall script saw the binary existed and short-circuited with "ready", even though it would fail at runtime.The fix adds a
sqliteLoadsSuccessfully()function that does a trialrequire()of the module. If it throws aNODE_MODULE_VERSIONerror, the script proceeds to rebuild instead of reporting success.Closes #1343
Test plan
node scripts/postinstall.cjs— should report "ready" without rebuildingnode scripts/postinstall.cjs— should detect ABI mismatch and trigger rebuild automaticallybuild/directory, runnode scripts/postinstall.cjs— should detect missing bindings and rebuild🤖 Generated with Claude Code