Refactor hooks and worker service for improved error handling and initialization

- Removed try-catch blocks in new-hook, save-hook, and summary-hook for cleaner flow.
- Enhanced error handling in save and summary hooks to throw errors instead of logging and returning.
- Introduced ensureWorkerRunning utility to manage worker service lifecycle and health checks.
- Replaced dynamic port allocation with a fixed port for the worker service.
- Simplified path management and removed unused port allocator utility.
- Added database schema initialization for fresh installations and improved migration handling.
This commit is contained in:
Alex Newman
2025-10-19 00:57:49 -04:00
parent cf9d1d4a0b
commit 7ff611feb5
23 changed files with 832 additions and 434 deletions
+18 -49
View File
@@ -4,26 +4,25 @@ import { existsSync, mkdirSync } from 'fs';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
// Get __dirname that works in both ESM (hooks) and CJS (worker) contexts
function getDirname(): string {
// CJS context - __dirname exists
if (typeof __dirname !== 'undefined') {
return __dirname;
}
// ESM context - use import.meta.url
return dirname(fileURLToPath(import.meta.url));
}
const _dirname = getDirname();
/**
* Simple path configuration for claude-mem
* Standard paths based on Claude Code conventions
*
* v4.0.0: Data directory now uses CLAUDE_PLUGIN_ROOT when available
*/
// Base directories
// Priority: CLAUDE_PLUGIN_ROOT/data > CLAUDE_MEM_DATA_DIR > ~/.claude-mem
const getDataDir = (): string => {
if (process.env.CLAUDE_PLUGIN_ROOT) {
return join(process.env.CLAUDE_PLUGIN_ROOT, 'data');
}
if (process.env.CLAUDE_MEM_DATA_DIR) {
return process.env.CLAUDE_MEM_DATA_DIR;
}
return join(homedir(), '.claude-mem');
};
export const DATA_DIR = getDataDir();
export const DATA_DIR = process.env.CLAUDE_MEM_DATA_DIR || join(homedir(), '.claude-mem');
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
// Data subdirectories
@@ -53,13 +52,6 @@ export function getWorkerSocketPath(sessionId: number): string {
return join(DATA_DIR, `worker-${sessionId}.sock`);
}
/**
* Get worker port file path
*/
export function getWorkerPortFilePath(): string {
return join(DATA_DIR, 'worker.port');
}
/**
* Ensure a directory exists
*/
@@ -103,36 +95,13 @@ export function getCurrentProjectName(): string {
}
/**
* Find package root directory (for install command)
* Find package root directory
*
* Works because bundled hooks are in plugin/scripts/,
* so package root is always two levels up
*/
export function getPackageRoot(): string {
// Method 1: Try require.resolve for package.json
try {
const packageJsonPath = require.resolve('claude-mem/package.json');
return dirname(packageJsonPath);
} catch {
// Continue to next method
}
// Method 2: Walk up from current module location
const currentFile = fileURLToPath(import.meta.url);
let currentDir = dirname(currentFile);
for (let i = 0; i < 10; i++) {
const packageJsonPath = join(currentDir, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson = require(packageJsonPath);
if (packageJson.name === 'claude-mem') {
return currentDir;
}
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) break;
currentDir = parentDir;
}
throw new Error('Cannot locate claude-mem package root. Ensure claude-mem is properly installed.');
return join(_dirname, '..', '..');
}
/**
+107
View File
@@ -0,0 +1,107 @@
import path from 'path';
import { existsSync } from 'fs';
import { spawn } from 'child_process';
import { getPackageRoot } from './paths.js';
const FIXED_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || '37777', 10);
const HEALTH_CHECK_URL = `http://127.0.0.1:${FIXED_PORT}/health`;
/**
* Check if worker is responding by hitting health endpoint
*/
async function checkWorkerHealth(): Promise<boolean> {
try {
const response = await fetch(HEALTH_CHECK_URL, {
signal: AbortSignal.timeout(500)
});
return response.ok;
} catch {
return false;
}
}
/**
* Ensure worker service is running with retry logic
* Auto-starts worker if not running (v4.0.0 feature)
*
* @returns true if worker is responding, false if failed to start
*/
export async function ensureWorkerRunning(): Promise<boolean> {
try {
// Check if worker is already responding
if (await checkWorkerHealth()) {
return true;
}
console.error('[claude-mem] Worker not responding, starting...');
// Find worker service path
const packageRoot = getPackageRoot();
const workerPath = path.join(packageRoot, 'dist', 'worker-service.cjs');
if (!existsSync(workerPath)) {
console.error(`[claude-mem] Worker service not found at ${workerPath}`);
return false;
}
// Try to start with PM2 first (preferred for production)
const ecosystemPath = path.join(packageRoot, 'ecosystem.config.cjs');
if (existsSync(ecosystemPath)) {
try {
spawn('pm2', ['start', ecosystemPath], {
detached: true,
stdio: 'ignore',
cwd: packageRoot
}).unref();
console.error('[claude-mem] Worker started with PM2');
} catch (pm2Error) {
console.error('[claude-mem] PM2 not available, using direct spawn');
// Fallback: spawn worker directly
spawn('node', [workerPath], {
detached: true,
stdio: 'ignore',
env: { ...process.env, NODE_ENV: 'production', CLAUDE_MEM_WORKER_PORT: FIXED_PORT.toString() }
}).unref();
console.error('[claude-mem] Worker started in background');
}
} else {
// No PM2 config, spawn directly
spawn('node', [workerPath], {
detached: true,
stdio: 'ignore',
env: { ...process.env, NODE_ENV: 'production', CLAUDE_MEM_WORKER_PORT: FIXED_PORT.toString() }
}).unref();
console.error('[claude-mem] Worker started in background');
}
// Wait for worker to become healthy (retry 3 times with 500ms delay)
for (let i = 0; i < 3; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
if (await checkWorkerHealth()) {
console.error('[claude-mem] Worker is healthy');
return true;
}
}
console.error('[claude-mem] Worker failed to become healthy after startup');
return false;
} catch (error: any) {
console.error(`[claude-mem] Failed to start worker: ${error.message}`);
return false;
}
}
/**
* Check if worker is currently running
*/
export async function isWorkerRunning(): Promise<boolean> {
return checkWorkerHealth();
}
/**
* Get the worker port number (fixed port)
*/
export function getWorkerPort(): number {
return FIXED_PORT;
}