Skip to content

fix: detect and rebuild better-sqlite3 on Node.js ABI mismatch#1373

Open
zerone0x wants to merge 1 commit intoMemTensor:mainfrom
zerone0x:fix/sqlite-abi-mismatch-rebuild
Open

fix: detect and rebuild better-sqlite3 on Node.js ABI mismatch#1373
zerone0x wants to merge 1 commit intoMemTensor:mainfrom
zerone0x:fix/sqlite-abi-mismatch-rebuild

Conversation

@zerone0x
Copy link
Copy Markdown
Contributor

Summary

  • Fixes the NODE_MODULE_VERSION mismatch that prevents memos-local-openclaw-plugin from loading after a Node.js or plugin update on Windows (and other platforms)
  • The postinstall.cjs script previously only checked whether the .node binary file existed on disk; it now also attempts to require() the module and catches ABI mismatch errors
  • When an incompatible binary is detected, npm rebuild better-sqlite3 runs automatically — no manual intervention needed

Details

The root cause: openclaw plugins update installs 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 trial require() of the module. If it throws a NODE_MODULE_VERSION error, the script proceeds to rebuild instead of reporting success.

Closes #1343

Test plan

  • On a machine with better-sqlite3 already built, run node scripts/postinstall.cjs — should report "ready" without rebuilding
  • Swap to a different Node.js version (e.g. via nvm), run node scripts/postinstall.cjs — should detect ABI mismatch and trigger rebuild automatically
  • Delete the build/ directory, run node scripts/postinstall.cjs — should detect missing bindings and rebuild

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings March 27, 2026 15:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to require() better-sqlite3 to catch ABI mismatch failures even when a .node file 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.

Comment on lines +254 to +257
} else if (!sqliteLoadsSuccessfully()) {
log("Native binding file exists but cannot be loaded by current Node.js.");
needsRebuild = true;
} else {
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +248 to +256
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;
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.");

Copilot uses AI. Check for mistakes.
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.
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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}`);

Copilot uses AI. Check for mistakes.
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.

[bug]better-sqlite3 ABI mismatch after updating memos-local-openclaw-plugin on Windows; manual rebuild required

2 participants