thedotmack/claude-mem · error · Error

Worker already running

Error message

Worker already running

What it means

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.

Solutions

  1. Stop the already-running worker process (find it via the PID in the worker PID file) and start again
  2. Remove the stale PID file only after confirming the process is dead, then retry
  3. Use a process manager (systemd/launchd) to ensure only one worker instance runs

Example fix

// before
supervisor.start(); // throws if pidfile is alive
// after
if (validateWorkerPidFile({ logAlive: false }) === 'alive') {
  await stopExistingWorker();
}
supervisor.start();
Defensive patterns

Strategy: try-catch

Validate before calling

import { validateWorkerPidFile } from './pidfile';
const canStart = validateWorkerPidFile({ logAlive: false }) !== 'alive';

Try / catch

try { await supervisor.start(); }
catch (e) { if (e.message === 'Worker already running') { console.error('worker pid:', readPidFile()); process.exit(1); } throw e; }

Prevention

When it happens

Trigger: Calling supervisor.start() when a previous worker instance is still running and its PID file points to a live process.

Common situations: 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.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/9cdb8695d8eaf87a. Report an issue: GitHub.

Appendix: source

Thrown at src/supervisor/index.ts:42

class Supervisor {
  private readonly registry: ProcessRegistry;
  private started = false;
  private stopPromise: Promise<void> | null = null;
  private signalHandlersRegistered = false;
  private shutdownInitiated = false;
  private shutdownHandler: (() => Promise<void>) | null = null;

  constructor(registry: ProcessRegistry) {
    this.registry = registry;
  }

  async start(): Promise<void> {
    if (this.started) return;

    this.registry.initialize();
    const pidStatus = validateWorkerPidFile({ logAlive: false });
    if (pidStatus === 'alive') {
      throw new Error('Worker already running');
    }

    this.started = true;

    // Reap chroma-mcp trees that no worker owns (#3905). Detached and best-effort: the sweep reads
    // the process table, so it must never gate boot, and a failure leaves the pre-sweep state.
    // It runs here, after initialize() and before anything of ours is spawned, so every signature
    // tree in the table with a dead or PID-1 parent is by construction someone else's leftover.
    void sweepOrphanedChromaTrees({ registry: this.registry }).catch((error: unknown) => {
      logger.warn('PROCESS', 'Orphaned chroma-mcp sweep failed', {
        error: error instanceof Error ? error.message : String(error),
      });
    });

    startHealthChecker();
  }

  configureSignalHandlers(shutdownHandler: () => Promise<void>): void {

View on GitHub (pinned to d8bc9755e7)