thedotmack/claude-mem · warning

Port still bound entering restart fallback — verifying curre

Error message

Port still bound entering restart fallback — verifying current port owner instead of spawning

What it means

In the restart fallback path, if the port was never observed free, spawning a competitor is pointless — it could not bind anyway. The CLI instead verifies whichever process currently owns the port: either the stuck old worker (verification fails and reports its health payload) or an already-running successor it failed to observe in time (verification passes).

Source

Thrown at src/services/worker-service.ts:1177

      const restartScript = resolveWorkerScriptPath() ?? __filename;
      let spawnedScript = 'none (port still bound — nothing spawned)';
      let spawnLockHeld = false;
      if (restartFreed) {
        // Owner-or-dead guarded (Phase 5): delete only the old worker's PID
        // file (oldPid) or a dead pid's leftover. If a successor we failed to
        // observe already wrote its own file, it must survive this cleanup.
        removePidFileIfOwner(oldPid);
        // Spawn gate (src/shared/worker-spawn-gate.ts): if another launcher
        // (a hook or the MCP server) is already mid-spawn, skip our own spawn
        // and just verify its worker below.
        spawnLockHeld = acquireSpawnLock();
      } else {
        // The port never freed: either the old worker refuses to die (the
        // verification below fails and reports its health payload) or a
        // successor we failed to observe in time already owns the port (the
        // verification below passes). Spawning a competitor here is never
        // useful — it could not bind the port anyway.
        logger.warn('SYSTEM', 'Port still bound entering restart fallback — verifying current port owner instead of spawning', { port, portWaitSkipped: handoffSawLiveWorker });
      }
      try {
        if (spawnLockHeld) {
          const restartPid = spawnDaemon(restartScript, port);
          if (restartPid === undefined) {
            console.error('Failed to spawn worker daemon during restart.');
            // Manual release: process.exit() does not unwind to finally.
            releaseSpawnLock();
            process.exit(1);
          }
          spawnedScript = restartScript;
          logger.info('SYSTEM', 'Worker restart spawned (CLI fallback)', { pid: restartPid, script: restartScript });
          // Hold the lock until the spawned worker owns the port (the spawn
          // isn't "done" until then — same rule as the other gated
          // launchers); the longer verification below runs unlocked.
          await waitForHealth(port, getPlatformTimeout(15000));
        } else if (restartFreed) {
          spawnedScript = 'none (another launcher holds the spawn lock)';

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Check `claude-mem status` — if the current owner is a healthy new-version worker, the restart effectively succeeded
  2. If the old worker is the stuck owner, run `claude-mem stop` (or kill its pid) and then `claude-mem start`
  3. Passive warning: if everything is healthy afterwards, no action needed
Defensive patterns

Strategy: validation

Validate before calling

async function describePortOwner(port: number): Promise<{ pid: number; healthy: boolean; version?: string }> {
  const pid = await getCurrentWorkerPid(port, 2000);
  let healthy = false, version: string | undefined;
  try {
    const res = await fetch(`http://127.0.0.1:${port}/health`);
    healthy = res.ok;
    version = (await res.json()).version;
  } catch {}
  return { pid: pid ?? -1, healthy, version };
}

Prevention

When it happens

Trigger: The old worker is slow to release the port while a successor from the restart handoff has already bound it; or the old worker is genuinely stuck and still owns the listener.

Common situations: Restart racing the dying worker's cleanup; a successor started by a hook's lazy-spawn owning the port before the CLI noticed; overloaded machines stretching the port-free window.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/34baa6a25414b177. Report an issue: GitHub.