From 6c9a8d1dca6cfa887a4c2057f806f2bf44df24d9 Mon Sep 17 00:00:00 2001 From: Dmitry Guyvoronsky Date: Tue, 18 Nov 2025 12:20:41 -0800 Subject: [PATCH 1/3] fix: improve worker startup on fresh install This fixes the issue where the worker service wouldn't start automatically after installing the plugin, leaving users with confusing error messages. Changes: - Update worker-utils.ts to use local PM2 from node_modules instead of requiring it in PATH - Improve error messages to suggest npx pm2 commands - Add auto-start attempt in smart-install.js after fresh installation - Fall back gracefully if worker can't start (will auto-start on first use) Fixes issue where users with PM2 not in their PATH couldn't start the worker. --- scripts/smart-install.js | 23 ++++++++++++++++++++--- src/shared/worker-utils.ts | 13 ++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/scripts/smart-install.js b/scripts/smart-install.js index 83c1c877..e238345d 100644 --- a/scripts/smart-install.js +++ b/scripts/smart-install.js @@ -260,10 +260,27 @@ async function main() { log('', colors.reset); process.exit(1); } - } - // Worker will be started lazily when needed (e.g., when save-hook sends data) - // Context hook only needs database access, not the worker service + // Try to start the PM2 worker after fresh install + try { + log('🚀 Starting worker service...', colors.cyan); + const localPm2 = join(NODE_MODULES_PATH, '.bin', 'pm2'); + const pm2Command = existsSync(localPm2) ? localPm2 : 'pm2'; + const ecosystemPath = join(PLUGIN_ROOT, 'ecosystem.config.cjs'); + + execSync(`"${pm2Command}" start "${ecosystemPath}"`, { + cwd: PLUGIN_ROOT, + stdio: 'pipe', + encoding: 'utf-8' + }); + + log('✅ Worker service started', colors.green); + } catch (error) { + // Worker might already be running or PM2 not available - that's okay + // The ensureWorkerRunning() function will handle auto-start when needed + log('â„šī¸ Worker will start automatically when needed', colors.dim); + } + } // Success - dependencies installed (if needed) process.exit(0); diff --git a/src/shared/worker-utils.ts b/src/shared/worker-utils.ts index 908ca35c..f471390e 100644 --- a/src/shared/worker-utils.ts +++ b/src/shared/worker-utils.ts @@ -55,9 +55,13 @@ async function startWorker(): Promise { throw new Error(`Ecosystem config not found at ${ecosystemPath}`); } + // Try to use local PM2 from node_modules first, fall back to global PM2 + const localPm2 = path.join(pluginRoot, 'node_modules', '.bin', 'pm2'); + const pm2Command = existsSync(localPm2) ? localPm2 : 'pm2'; + // Start using PM2 with the ecosystem config // CRITICAL: Must set cwd to pluginRoot so PM2 starts from marketplace directory - execSync(`pm2 start "${ecosystemPath}"`, { + execSync(`"${pm2Command}" start "${ecosystemPath}"`, { cwd: pluginRoot, stdio: 'pipe', encoding: 'utf-8' @@ -93,10 +97,13 @@ export async function ensureWorkerRunning(): Promise { if (!started) { const port = getWorkerPort(); + const pluginRoot = getPackageRoot(); throw new Error( `Worker service failed to start on port ${port}.\n\n` + - `Try manually running: pm2 start ecosystem.config.cjs\n` + - `Or restart: pm2 restart claude-mem-worker` + `To start manually, run:\n` + + ` cd ${pluginRoot}\n` + + ` npx pm2 start ecosystem.config.cjs\n\n` + + `If already running, try: npx pm2 restart claude-mem-worker` ); } } From 2a2206d886278d3e3c32e2b97f9bed188af5f7fd Mon Sep 17 00:00:00 2001 From: Dmitry Guyvoronsky Date: Mon, 24 Nov 2025 13:27:46 -0800 Subject: [PATCH 2/3] fix: add Windows compatibility for PM2 detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use .cmd extension for PM2 on Windows (pm2.cmd vs pm2) - Add shell: true to execSync for proper path quoting on Windows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/smart-install.js | 10 +++++++--- src/shared/worker-utils.ts | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/smart-install.js b/scripts/smart-install.js index e238345d..77cfee11 100644 --- a/scripts/smart-install.js +++ b/scripts/smart-install.js @@ -264,14 +264,18 @@ async function main() { // Try to start the PM2 worker after fresh install try { log('🚀 Starting worker service...', colors.cyan); - const localPm2 = join(NODE_MODULES_PATH, '.bin', 'pm2'); - const pm2Command = existsSync(localPm2) ? localPm2 : 'pm2'; + // On Windows, PM2 executable is pm2.cmd, not pm2 + const localPm2Base = join(NODE_MODULES_PATH, '.bin', 'pm2'); + const localPm2Cmd = process.platform === 'win32' ? localPm2Base + '.cmd' : localPm2Base; + const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2'; const ecosystemPath = join(PLUGIN_ROOT, 'ecosystem.config.cjs'); + // shell: true required for Windows to handle quoted paths correctly execSync(`"${pm2Command}" start "${ecosystemPath}"`, { cwd: PLUGIN_ROOT, stdio: 'pipe', - encoding: 'utf-8' + encoding: 'utf-8', + shell: true }); log('✅ Worker service started', colors.green); diff --git a/src/shared/worker-utils.ts b/src/shared/worker-utils.ts index f471390e..7b8d0e49 100644 --- a/src/shared/worker-utils.ts +++ b/src/shared/worker-utils.ts @@ -56,15 +56,19 @@ async function startWorker(): Promise { } // Try to use local PM2 from node_modules first, fall back to global PM2 - const localPm2 = path.join(pluginRoot, 'node_modules', '.bin', 'pm2'); - const pm2Command = existsSync(localPm2) ? localPm2 : 'pm2'; + // On Windows, PM2 executable is pm2.cmd, not pm2 + const localPm2Base = path.join(pluginRoot, 'node_modules', '.bin', 'pm2'); + const localPm2Cmd = process.platform === 'win32' ? localPm2Base + '.cmd' : localPm2Base; + const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2'; // Start using PM2 with the ecosystem config // CRITICAL: Must set cwd to pluginRoot so PM2 starts from marketplace directory + // shell: true required for Windows to handle quoted paths correctly execSync(`"${pm2Command}" start "${ecosystemPath}"`, { cwd: pluginRoot, stdio: 'pipe', - encoding: 'utf-8' + encoding: 'utf-8', + shell: true }); // Wait for worker to become healthy From a48a67ca768c411586529f73d7b217be6ac11a0a Mon Sep 17 00:00:00 2001 From: Dmitry Guyvoronsky Date: Tue, 25 Nov 2025 16:57:10 -0800 Subject: [PATCH 3/3] fix: use spawnSync to avoid command injection risks Replace execSync with shell string interpolation with spawnSync and array arguments. This eliminates potential command injection if paths contain special characters. --- scripts/smart-install.js | 12 +++++++----- src/shared/worker-utils.ts | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/scripts/smart-install.js b/scripts/smart-install.js index 77cfee11..5f24987d 100644 --- a/scripts/smart-install.js +++ b/scripts/smart-install.js @@ -12,7 +12,7 @@ */ import { existsSync, readFileSync, writeFileSync } from 'fs'; -import { execSync } from 'child_process'; +import { execSync, spawnSync } from 'child_process'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; @@ -270,13 +270,15 @@ async function main() { const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2'; const ecosystemPath = join(PLUGIN_ROOT, 'ecosystem.config.cjs'); - // shell: true required for Windows to handle quoted paths correctly - execSync(`"${pm2Command}" start "${ecosystemPath}"`, { + // Using spawnSync with array args to avoid command injection risks + const result = spawnSync(pm2Command, ['start', ecosystemPath], { cwd: PLUGIN_ROOT, stdio: 'pipe', - encoding: 'utf-8', - shell: true + encoding: 'utf-8' }); + if (result.status !== 0) { + throw new Error(result.stderr || 'PM2 start failed'); + } log('✅ Worker service started', colors.green); } catch (error) { diff --git a/src/shared/worker-utils.ts b/src/shared/worker-utils.ts index 7b8d0e49..ed12e68f 100644 --- a/src/shared/worker-utils.ts +++ b/src/shared/worker-utils.ts @@ -1,7 +1,7 @@ import path from "path"; import { homedir } from "os"; import { existsSync, readFileSync } from "fs"; -import { execSync } from "child_process"; +import { spawnSync } from "child_process"; import { getPackageRoot } from "./paths.js"; // Named constants for health checks @@ -63,13 +63,15 @@ async function startWorker(): Promise { // Start using PM2 with the ecosystem config // CRITICAL: Must set cwd to pluginRoot so PM2 starts from marketplace directory - // shell: true required for Windows to handle quoted paths correctly - execSync(`"${pm2Command}" start "${ecosystemPath}"`, { + // Using spawnSync with array args to avoid command injection risks + const result = spawnSync(pm2Command, ['start', ecosystemPath], { cwd: pluginRoot, stdio: 'pipe', - encoding: 'utf-8', - shell: true + encoding: 'utf-8' }); + if (result.status !== 0) { + throw new Error(result.stderr || 'PM2 start failed'); + } // Wait for worker to become healthy for (let i = 0; i < WORKER_STARTUP_RETRIES; i++) {