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
When better-sqlite3 (or any native dep) fails to build on a user's machine, the failure is completely invisible from Claude Code's perspective. The user sees a vague "SessionStart hook error" with no indication that episodic-memory is involved, let alone which native module is broken. The plugin then silently stops archiving conversations for every future session until someone manually inspects ~/.config/superpowers/logs/episodic-memory.log.
I lost ~30 minutes to this today before tracing it to the actual error. Diagnostics paste at the bottom.
This issue is about the observability failure mode, not about fixing my specific install (npm rebuild better-sqlite3 resolved it locally). Prior install-side fixes (#56, #76/#86) won't help the next user who hits a different native-build failure — the silent-fail path will swallow that too.
Environment
Plugin version: 1.3.1
Plugin source: superpowers-marketplace
OS: Linux 6.6.87.2-microsoft-standard-WSL2 (Ubuntu under WSL2)
Node: v22.22.2
Claude Code: latest as of 2026-05-19
What the user sees vs what's actually happening
User-visible: Claude Code surfaces SessionStart:startup hook error. No plugin name, no stack, no path to investigate. The session otherwise appears to start normally.
Parent exits 0 immediately (because --background detaches the child). From Claude Code's view, the hook succeeded.
The detached child tries require('better-sqlite3'), throws Error: Could not locate the bindings file, writes the stack to ~/.config/superpowers/logs/episodic-memory.log, and dies.
Every subsequent SessionStart repeats step 1–3. Conversations stop being archived. The user has no idea.
The "SessionStart:startup hook error" Claude Code does show must be coming from somewhere else on the path (possibly a sibling hook or the non-JSON stdout Sync started in background. Log: ... line). Either way, the message points at no useful target.
Root cause of the invisibility
Two design choices compound:
postinstall swallows build errors. The install path runs npm rebuild better-sqlite3 2>/dev/null || true — the 2>/dev/null || true makes a failed rebuild indistinguishable from success. npm install hangs indefinitely on macOS causing high load and massive disk I/O #41 alluded to this script's existence but the resolution there was about the macOS retry loop, not the silent-fail semantics.
The SessionStart hook is fire-and-forget.sync --background detaches before doing any work that could fail. There is no preflight check that the database layer is even loadable. By the time the real error happens, the hook has already exited 0.
The combination means: a broken install reports as healthy, then a broken runtime reports as healthy, every session, forever.
Sentinel file on background failure. If preflight in the hook isn't acceptable for latency reasons, the background child could write ~/.config/superpowers/logs/last-sync-error.txt on crash, and the next SessionStart hook reads it and surfaces it via additionalContext. Same outcome, no synchronous cost on the happy path.
The framing I'd push for: failure-to-archive should never be silent. It's a passive feature; if it's broken, the user only finds out the day they try to search past conversations and discover none of the last 6 months exist.
Reproduction
Easiest way to reproduce on a clean Node install where prebuild download fails (or in WSL2 where I hit it organically):
# Pretend the binding never built
rm -rf ~/.claude/plugins/cache/superpowers-marketplace/episodic-memory/1.3.1/node_modules/better-sqlite3/build
# Start a new Claude Code session# → Claude Code: "SessionStart:startup hook error" (no further info)# → Log: ~/.config/superpowers/logs/episodic-memory.log shows the real error# → No way for the user to know which plugin is responsible
Diagnostics (what I actually saw)
Error syncing: Error: Could not locate the bindings file. Tried:
→ .../node_modules/better-sqlite3/build/better_sqlite3.node
→ .../node_modules/better-sqlite3/build/Release/better_sqlite3.node
→ .../node_modules/better-sqlite3/lib/binding/node-v127-linux-x64/better_sqlite3.node
[12 more paths]
at bindings (.../bindings.js:126:9)
at new Database (.../better-sqlite3/lib/database.js:48:64)
at initDatabase (.../dist/db.js:100:16)
at syncConversations (.../dist/sync.js:115:20)
at async syncAll (.../dist/sync-cli.js:88:24)
Fix locally:
cd ~/.claude/plugins/cache/superpowers-marketplace/episodic-memory/1.3.1
npm rebuild better-sqlite3
This issue is distinct from all five — they each address one install failure mode. This issue is about the observability layer that hides every install failure mode, current and future.
Summary
When
better-sqlite3(or any native dep) fails to build on a user's machine, the failure is completely invisible from Claude Code's perspective. The user sees a vague "SessionStart hook error" with no indication that episodic-memory is involved, let alone which native module is broken. The plugin then silently stops archiving conversations for every future session until someone manually inspects~/.config/superpowers/logs/episodic-memory.log.I lost ~30 minutes to this today before tracing it to the actual error. Diagnostics paste at the bottom.
This issue is about the observability failure mode, not about fixing my specific install (
npm rebuild better-sqlite3resolved it locally). Prior install-side fixes (#56, #76/#86) won't help the next user who hits a different native-build failure — the silent-fail path will swallow that too.Environment
superpowers-marketplaceWhat the user sees vs what's actually happening
User-visible: Claude Code surfaces
SessionStart:startup hook error. No plugin name, no stack, no path to investigate. The session otherwise appears to start normally.Actually happening:
hooks/hooks.jsonrunsnode .../cli/episodic-memory.js sync --background.--backgrounddetaches the child). From Claude Code's view, the hook succeeded.require('better-sqlite3'), throwsError: Could not locate the bindings file, writes the stack to~/.config/superpowers/logs/episodic-memory.log, and dies.The "SessionStart:startup hook error" Claude Code does show must be coming from somewhere else on the path (possibly a sibling hook or the non-JSON stdout
Sync started in background. Log: ...line). Either way, the message points at no useful target.Root cause of the invisibility
Two design choices compound:
postinstallswallows build errors. The install path runsnpm rebuild better-sqlite3 2>/dev/null || true— the2>/dev/null || truemakes a failed rebuild indistinguishable from success. npm install hangs indefinitely on macOS causing high load and massive disk I/O #41 alluded to this script's existence but the resolution there was about the macOS retry loop, not the silent-fail semantics.The SessionStart hook is fire-and-forget.
sync --backgrounddetaches before doing any work that could fail. There is no preflight check that the database layer is even loadable. By the time the real error happens, the hook has already exited 0.The combination means: a broken install reports as healthy, then a broken runtime reports as healthy, every session, forever.
What I'd love to see (not prescriptive)
Picking one or two of these would close the gap:
Drop
2>/dev/null || truefrom the rebuild step. Build failures during install should be loud. If retry-loop concerns (npm install hangs indefinitely on macOS causing high load and massive disk I/O #41) are still live, gate retries elsewhere — not by silencing stderr.Preflight in the SessionStart hook. Before spawning the background sync, attempt a cheap
require('better-sqlite3')(and the other natives). If it throws, emithookSpecificOutput.additionalContextwith the actual error and a one-liner remediation (cd <plugin-dir> && npm rebuild). Cost: one require, ~tens of ms. Benefit: every silent-fail surface I've seen reported (Node.js version of better-sqlite3 has a problem with newer node.js versions #9, Relies on better-sqlite3 which causes endless npm install when plugin runs for the first time #19, this one, likely future ones) collapses into a single visible message.Sentinel file on background failure. If preflight in the hook isn't acceptable for latency reasons, the background child could write
~/.config/superpowers/logs/last-sync-error.txton crash, and the next SessionStart hook reads it and surfaces it viaadditionalContext. Same outcome, no synchronous cost on the happy path.The framing I'd push for: failure-to-archive should never be silent. It's a passive feature; if it's broken, the user only finds out the day they try to search past conversations and discover none of the last 6 months exist.
Reproduction
Easiest way to reproduce on a clean Node install where prebuild download fails (or in WSL2 where I hit it organically):
Diagnostics (what I actually saw)
Fix locally:
Prior art I checked first
2>/dev/null || truepostinstall script--prefer-offlineETARGET in the MCP wrapperThis issue is distinct from all five — they each address one install failure mode. This issue is about the observability layer that hides every install failure mode, current and future.