You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scripts/postinstall.js rebuilds the native binding with npm rebuild better-sqlite3. On Node 25 (macOS arm64, better-sqlite3 12.10.0) that command is a silent no-op: it prints rebuilt dependencies successfully, exits 0, and produces no build/Release/better_sqlite3.node. So a fresh plugin install / update ships with no loadable binding, every SessionStart sync dies with Could not locate the bindings file, and the MCP search/read tools return nothing.
This is the install-side root cause underneath #94. #94 is about the failure being invisible and explicitly assumes npm rebuild better-sqlite3 is the working local fix — but that reporter was on Node 22 / Linux. On Node 25 the rebuild command itself doesn't work, so the postinstall can never succeed and the recovery hint it prints is dead.
Environment
Plugin version: 1.4.2 (also reproduced against the 1.4.2 node_modules)
Plugin source: superpowers-marketplace
OS: macOS (Darwin 25.5.0), arm64
Node: v25.2.1 (NODE_MODULE_VERSION 141)
better-sqlite3: 12.10.0
Xcode CLT + python3 present (node-gyp can build)
Root cause (empirical)
better-sqlite3's own install script is prebuild-install || node-gyp rebuild --release. The postinstall doesn't invoke that — it invokes npm rebuild better-sqlite3, and on this setup npm rebuild never runs the package's install script:
$ rm -f node_modules/better-sqlite3/build/Release/better_sqlite3.node
$ npm rebuild better-sqlite3 --foreground-scripts
rebuilt dependencies successfully # exit 0
$ ls node_modules/better-sqlite3/build/Release/better_sqlite3.node
ls: ...better_sqlite3.node: No such file or directory # nothing built
The two commands that DO produce a working binding, run directly in the better-sqlite3 dir:
$ ./node_modules/.bin/prebuild-install # places a prebuilt, exit 0
# or
$ npm run build-release # = node-gyp rebuild --release, compiles locally
After npm run build-release, new Database(':memory:') loads and queries fine.
npm rebuild's no-op exits 0, so status !== 0 is never true — the failure is undetectable by exit code. The warning branch (which prints Recover with: cd <plugin-dir> && npm rebuild better-sqlite3) both never fires here AND recommends the same command that doesn't work on Node 25.
A second trap for anyone writing a health check: require('better-sqlite3') succeeds even with no binding, because better-sqlite3 loads the native addon lazily inside new Database(). So a require()-based preflight passes on a broken install. The binding only actually loads when a Database is instantiated.
Suggested direction (not prescriptive)
The robust check is "did the binding actually load," not "did the rebuild command exit 0":
In postinstall, after the rebuild, verify by instantiatingnew (require('better-sqlite3'))(':memory:') in a child process. If it throws, fall back to node-gyp rebuild --release (or run the package's own install script / prebuild-install) and re-verify.
Prefer invoking better-sqlite3's actual install path (prebuild-install || node-gyp rebuild --release) over npm rebuild, which (at least on npm shipped with Node 25) doesn't run that package's install script.
PLUGIN=~/.claude/plugins/cache/superpowers-marketplace/episodic-memory/1.4.2
rm -f "$PLUGIN/node_modules/better-sqlite3/build/Release/better_sqlite3.node"
( cd"$PLUGIN/node_modules/better-sqlite3"&& npm rebuild better-sqlite3 ) # "success", builds nothing
node -e "new (require('$PLUGIN/node_modules/better-sqlite3'))(':memory:')"# Could not locate the bindings file
( cd"$PLUGIN/node_modules/better-sqlite3"&& npm run build-release ) # this actually builds it
Summary
scripts/postinstall.jsrebuilds the native binding withnpm rebuild better-sqlite3. On Node 25 (macOS arm64, better-sqlite3 12.10.0) that command is a silent no-op: it printsrebuilt dependencies successfully, exits 0, and produces nobuild/Release/better_sqlite3.node. So a fresh plugin install / update ships with no loadable binding, every SessionStart sync dies withCould not locate the bindings file, and the MCP search/read tools return nothing.This is the install-side root cause underneath #94. #94 is about the failure being invisible and explicitly assumes
npm rebuild better-sqlite3is the working local fix — but that reporter was on Node 22 / Linux. On Node 25 the rebuild command itself doesn't work, so the postinstall can never succeed and the recovery hint it prints is dead.Environment
node_modules)superpowers-marketplaceRoot cause (empirical)
better-sqlite3's own install script is
prebuild-install || node-gyp rebuild --release. The postinstall doesn't invoke that — it invokesnpm rebuild better-sqlite3, and on this setupnpm rebuildnever runs the package's install script:The two commands that DO produce a working binding, run directly in the better-sqlite3 dir:
After
npm run build-release,new Database(':memory:')loads and queries fine.Why postinstall.js can't catch it
npm rebuild's no-op exits 0, sostatus !== 0is never true — the failure is undetectable by exit code. The warning branch (which printsRecover with: cd <plugin-dir> && npm rebuild better-sqlite3) both never fires here AND recommends the same command that doesn't work on Node 25.A second trap for anyone writing a health check:
require('better-sqlite3')succeeds even with no binding, because better-sqlite3 loads the native addon lazily insidenew Database(). So arequire()-based preflight passes on a broken install. The binding only actually loads when aDatabaseis instantiated.Suggested direction (not prescriptive)
The robust check is "did the binding actually load," not "did the rebuild command exit 0":
new (require('better-sqlite3'))(':memory:')in a child process. If it throws, fall back tonode-gyp rebuild --release(or run the package's owninstallscript /prebuild-install) and re-verify.prebuild-install || node-gyp rebuild --release) overnpm rebuild, which (at least on npm shipped with Node 25) doesn't run that package's install script.npm rebuild ... 2>/dev/null || true+--backgroundhook hide native-binding errors from users #94: the same instantiate-to-verify check, run in the SessionStart hook, surfaces a real remediation instead of a silent log line.Reproduction
Prior art checked
npm rebuild ... 2>/dev/null || true+--backgroundhook hide native-binding errors from users #94 — observability of the same failure; assumesnpm rebuildis the fix (Node 22 / Linux). This issue is why that assumption doesn't hold on Node 25.