feat: Auto-detect and rebuild native modules on Node.js version changes (#149)

Implements three-layer defense against native module version mismatches:

Layer 1: Node.js Version Tracking
- Track Node.js version alongside package version in .install-version marker
- Auto-trigger npm install when Node.js version changes
- Backward compatible with old plain-text version marker format

Layer 2: Native Module Verification
- Add verifyNativeModules() function to test better-sqlite3 loads correctly
- Verify after install completes to catch corrupted builds
- Retry with force flag if initial install verification fails

Layer 3: Graceful Failure
- Catch ERR_DLOPEN_FAILED in context-hook and delete version marker
- Exit cleanly to avoid error spam in Claude Code UI
- Auto-fix on next session start

Changes:
- scripts/smart-install.js: Add Node.js version tracking and verification
- src/hooks/context-hook.ts: Add graceful failure handling for native module errors
- tests/smart-install.test.js: Add tests for version marker format compatibility
- plugin/scripts/context-hook.js: Built output from TypeScript source

Fixes the issue where users see ERR_DLOPEN_FAILED errors after Node.js upgrades,
requiring manual npm install. Now automatically detects and fixes the issue.

Related design doc: docs/context/native-module-auto-fix-design.md
Implementation plan: docs/context/native-module-auto-fix-implementation.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alex Newman <thedotmack@gmail.com>
This commit is contained in:
Dmytro Gaivoronsky
2025-11-30 14:28:07 -08:00
committed by GitHub
parent de279ef6bf
commit 69b17e15a2
5 changed files with 221 additions and 37 deletions
+32 -2
View File
@@ -5,10 +5,20 @@
import path from 'path';
import { homedir } from 'os';
import { existsSync, readFileSync } from 'fs';
import { existsSync, readFileSync, unlinkSync } from 'fs';
import { stdin } from 'process';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { SessionStore } from '../services/sqlite/SessionStore.js';
// Get __dirname equivalent in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Version marker path (same as smart-install.js)
// From src/hooks/ we need to go up to plugin root: ../../
const VERSION_MARKER_PATH = path.join(__dirname, '../../.install-version');
/**
* Get context depth from settings
* Priority: ~/.claude/settings.json > env var > default
@@ -156,7 +166,27 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
const cwd = input?.cwd ?? process.cwd();
const project = cwd ? path.basename(cwd) : 'unknown-project';
const db = new SessionStore();
let db: SessionStore;
try {
db = new SessionStore();
} catch (error: any) {
if (error.code === 'ERR_DLOPEN_FAILED') {
// Native module ABI mismatch - delete version marker to trigger reinstall
try {
unlinkSync(VERSION_MARKER_PATH);
} catch (unlinkError) {
// Marker might not exist, that's okay
}
// Log once (not error spam) and exit cleanly
console.error('⚠️ Native module rebuild needed - restart Claude Code to auto-fix');
console.error(' (This happens after Node.js version upgrades)');
process.exit(0); // Exit cleanly to avoid error spam
}
// Other errors should still throw
throw error;
}
// Get ALL recent observations for this project (not filtered by summaries)
// This ensures we show observations even when summaries haven't been generated