{"record":{"id":"9cdb8695d8eaf87a","repo":"thedotmack/claude-mem","slug":"worker-already-running","errorCode":null,"errorMessage":"Worker already running","messagePattern":"Worker already running","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/supervisor/index.ts","lineNumber":42,"sourceCode":"class Supervisor {\n  private readonly registry: ProcessRegistry;\n  private started = false;\n  private stopPromise: Promise<void> | null = null;\n  private signalHandlersRegistered = false;\n  private shutdownInitiated = false;\n  private shutdownHandler: (() => Promise<void>) | null = null;\n\n  constructor(registry: ProcessRegistry) {\n    this.registry = registry;\n  }\n\n  async start(): Promise<void> {\n    if (this.started) return;\n\n    this.registry.initialize();\n    const pidStatus = validateWorkerPidFile({ logAlive: false });\n    if (pidStatus === 'alive') {\n      throw new Error('Worker already running');\n    }\n\n    this.started = true;\n\n    // Reap chroma-mcp trees that no worker owns (#3905). Detached and best-effort: the sweep reads\n    // the process table, so it must never gate boot, and a failure leaves the pre-sweep state.\n    // It runs here, after initialize() and before anything of ours is spawned, so every signature\n    // tree in the table with a dead or PID-1 parent is by construction someone else's leftover.\n    void sweepOrphanedChromaTrees({ registry: this.registry }).catch((error: unknown) => {\n      logger.warn('PROCESS', 'Orphaned chroma-mcp sweep failed', {\n        error: error instanceof Error ? error.message : String(error),\n      });\n    });\n\n    startHealthChecker();\n  }\n\n  configureSignalHandlers(shutdownHandler: () => Promise<void>): void {","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/supervisor/index.ts#L24-L60","documentation":"The supervisor refuses to start when a worker PID file indicates another worker process is already alive. validateWorkerPidFile checks whether the PID recorded on disk belongs to a live process; 'alive' means a previous instance is still running. This is a single-instance guard to prevent two workers from fighting over the same resources.","triggerScenarios":"Calling supervisor.start() when a previous worker instance is still running and its PID file points to a live process.","commonSituations":"Starting a second instance of the app while the first still runs; a stale-but-alive worker left over after a crashed parent; running dev and production instances against the same data directory.","solutions":["Stop the already-running worker process (find it via the PID in the worker PID file) and start again","Remove the stale PID file only after confirming the process is dead, then retry","Use a process manager (systemd/launchd) to ensure only one worker instance runs"],"exampleFix":"// before\nsupervisor.start(); // throws if pidfile is alive\n// after\nif (validateWorkerPidFile({ logAlive: false }) === 'alive') {\n  await stopExistingWorker();\n}\nsupervisor.start();","handlingStrategy":"try-catch","validationCode":"import { validateWorkerPidFile } from './pidfile';\nconst canStart = validateWorkerPidFile({ logAlive: false }) !== 'alive';","typeGuard":null,"tryCatchPattern":"try { await supervisor.start(); }\ncatch (e) { if (e.message === 'Worker already running') { console.error('worker pid:', readPidFile()); process.exit(1); } throw e; }","preventionTips":["Run the worker under a single-instance process manager","Always shut down via the supervisor's stop path so the PID file is cleaned","Check the PID file before attempting manual restarts"],"tags":["process","supervisor","singleton"],"backgroundTag":"address-already-in-use","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}