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:
+18
-49
@@ -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, '..', '..');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user