thedotmack/claude-mem · error

Cannot lazy-spawn worker: Bun runtime not found on PATH

Error message

Cannot lazy-spawn worker: Bun runtime not found on PATH

What it means

Lazy-spawn needs the Bun runtime to start the worker; resolveWorkerRuntimePath() returned null (no bun executable found on the PATH of the Claude Code process running hooks). The hook warns, returns false, and the whole hook event runs without worker-backed features (no memory injection).

Source

Thrown at src/shared/worker-utils.ts:553

      return false;
    }
    if (!(await waitForWorkerPortClosed())) {
      logger.error('SYSTEM', 'Stale worker port still open after SIGKILL; skipping spawn this hook event', {
        pid: stalePidInfo.pid,
        port: getWorkerPort(),
      });
      return false;
    }
    // The killed worker's PID file is left behind; the successor's boot
    // removes it (validateWorkerPidFile returns 'stale' for a dead pid).
    // Fall through to (re)spawn + readiness wait below.
  }

  const runtimePath = resolveWorkerRuntimePath();
  const scriptPath = resolvedScript?.scriptPath ?? null;

  if (!runtimePath) {
    logger.warn('SYSTEM', 'Cannot lazy-spawn worker: Bun runtime not found on PATH');
    return false;
  }
  if (!scriptPath) {
    logger.warn('SYSTEM', 'Cannot lazy-spawn worker: worker-service.cjs not found in plugin/scripts');
    return false;
  }

  // Spawn gate (worker-spawn-gate.ts): only ONE gated launcher — hook, MCP
  // server, or the CLI restart fallback — may spawn at a time. (The dying
  // worker's restart handoff in worker-shutdown.ts is deliberately NOT gated:
  // it is the spawner for CLI-initiated restarts. Hook version recycles never
  // trigger it — they SIGKILL the stale worker and spawn here.)
  // Losing the lock never fails the hook; the loser skips its spawn and waits
  // for the winner's worker on the existing port/readiness waits below. The
  // winner holds the lock through the port-open wait (the spawn isn't "done"
  // until the worker owns the port) and releases in finally on every exit
  // path.
  const spawnLockHeld = acquireSpawnLock();

View on GitHub (pinned to 8bc631a71a)

Solutions

  1. Install Bun (curl -fsSL https://bun.sh/install | bash) and fully restart the terminal/Claude so PATH updates propagate.
  2. Verify with `bun --version` run from the same environment that launches Claude Code.
  3. For GUI launches, make sure ~/.bun/bin is on the global PATH (launchctl setenv PATH, /etc/paths, or equivalent).
  4. Re-run npm run build-and-sync in the claude-mem checkout — its setup auto-installs Bun when missing.

Example fix

# before: bun only in an interactive shell profile
# (GUI-launched Claude Code never sources it)

# after: ensure global visibility
launchctl setenv PATH "$HOME/.bun/bin:$PATH"  # macOS GUI apps
# then restart Claude Desktop
Defensive patterns

Strategy: validation

Validate before calling

const runtimePath = resolveWorkerRuntimePath();
if (!runtimePath) {
  // install Bun or fix PATH before hooks need the worker
  failEarlyWithHint('Install Bun: curl -fsSL https://bun.sh/install | bash');
}

Type guard

const hasBunOnPath = (): boolean => resolveWorkerRuntimePath() !== null;

Prevention

When it happens

Trigger: Bun was never installed, was uninstalled, or exists only for a different user/shell so the Claude Code process's PATH does not include ~/.bun/bin. resolveWorkerRuntimePath() returns null and the spawn path aborts before resolving a script.

Common situations: Fresh machine; Bun installed via a shell config not sourced by GUI-launched Claude Desktop; PATH customized per-terminal so hooks see a different environment.

Related errors


AI-assisted analysis of thedotmack/claude-mem@8bc631a71a (2026-08-20). Data as JSON: /api/errors/8906aad46486a609. Report an issue: GitHub.