feat: auto-cleanup orphaned chroma-mcp processes on worker startup

Enhancement to process leak fix from v7.1.9 - automatically detects and
kills orphaned chroma-mcp processes when the worker starts.

Changes:
- Added cleanupOrphanedProcesses() method to WorkerService
- Scans for existing chroma-mcp processes on startup
- Kills all found processes before creating new ones
- Logs cleanup activity (process count and PIDs)
- Non-fatal error handling (continues on cleanup failure)

Benefits:
- Automatically recovers from pre-7.1.9 process leaks
- Ensures clean slate on every worker restart
- No manual intervention needed to cleanup orphans
- Prevents accumulation even if v7.1.9 close() fails

Verified working in logs:
[INFO] [SYSTEM] Cleaning up orphaned chroma-mcp processes {count=2, pids=33753,33750}
[INFO] [SYSTEM] Orphaned processes cleaned up {count=2}

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2025-12-13 20:21:21 -05:00
parent 0f3151cc2d
commit f41579b4d0
2 changed files with 110 additions and 56 deletions
File diff suppressed because one or more lines are too long
+53
View File
@@ -13,6 +13,10 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { getWorkerPort, getWorkerHost } from '../shared/worker-utils.js';
import { logger } from '../utils/logger.js';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
// Import composed domain services
import { DatabaseManager } from './worker/DatabaseManager.js';
@@ -149,6 +153,52 @@ export class WorkerService {
}
/**
* Clean up orphaned chroma-mcp processes from previous worker sessions
* Prevents process accumulation and memory leaks
*/
private async cleanupOrphanedProcesses(): Promise<void> {
try {
// Find all chroma-mcp processes
const { stdout } = await execAsync('ps aux | grep "chroma-mcp" | grep -v grep || true');
if (!stdout.trim()) {
logger.debug('SYSTEM', 'No orphaned chroma-mcp processes found');
return;
}
const lines = stdout.trim().split('\n');
const pids: number[] = [];
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length > 1) {
const pid = parseInt(parts[1], 10);
if (!isNaN(pid)) {
pids.push(pid);
}
}
}
if (pids.length === 0) {
return;
}
logger.info('SYSTEM', 'Cleaning up orphaned chroma-mcp processes', {
count: pids.length,
pids
});
// Kill all found processes
await execAsync(`kill ${pids.join(' ')}`);
logger.info('SYSTEM', 'Orphaned processes cleaned up', { count: pids.length });
} catch (error) {
// Non-fatal - log and continue
logger.warn('SYSTEM', 'Failed to cleanup orphaned processes', {}, error as Error);
}
}
/**
* Start the worker service
*/
@@ -173,6 +223,9 @@ export class WorkerService {
* Background initialization - runs after HTTP server is listening
*/
private async initializeBackground(): Promise<void> {
// Clean up any orphaned chroma-mcp processes BEFORE starting our own
await this.cleanupOrphanedProcesses();
// Initialize database (once, stays open)
await this.dbManager.initialize();