{"record":{"id":"34baa6a25414b177","repo":"thedotmack/claude-mem","slug":"port-still-bound-entering-restart-fallback-verif","errorCode":null,"errorMessage":"Port still bound entering restart fallback — verifying current port owner instead of spawning","messagePattern":"Port still bound entering restart fallback — verifying current port owner instead of spawning","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/worker-service.ts","lineNumber":1177,"sourceCode":"      const restartScript = resolveWorkerScriptPath() ?? __filename;\n      let spawnedScript = 'none (port still bound — nothing spawned)';\n      let spawnLockHeld = false;\n      if (restartFreed) {\n        // Owner-or-dead guarded (Phase 5): delete only the old worker's PID\n        // file (oldPid) or a dead pid's leftover. If a successor we failed to\n        // observe already wrote its own file, it must survive this cleanup.\n        removePidFileIfOwner(oldPid);\n        // Spawn gate (src/shared/worker-spawn-gate.ts): if another launcher\n        // (a hook or the MCP server) is already mid-spawn, skip our own spawn\n        // and just verify its worker below.\n        spawnLockHeld = acquireSpawnLock();\n      } else {\n        // The port never freed: either the old worker refuses to die (the\n        // verification below fails and reports its health payload) or a\n        // successor we failed to observe in time already owns the port (the\n        // verification below passes). Spawning a competitor here is never\n        // useful — it could not bind the port anyway.\n        logger.warn('SYSTEM', 'Port still bound entering restart fallback — verifying current port owner instead of spawning', { port, portWaitSkipped: handoffSawLiveWorker });\n      }\n      try {\n        if (spawnLockHeld) {\n          const restartPid = spawnDaemon(restartScript, port);\n          if (restartPid === undefined) {\n            console.error('Failed to spawn worker daemon during restart.');\n            // Manual release: process.exit() does not unwind to finally.\n            releaseSpawnLock();\n            process.exit(1);\n          }\n          spawnedScript = restartScript;\n          logger.info('SYSTEM', 'Worker restart spawned (CLI fallback)', { pid: restartPid, script: restartScript });\n          // Hold the lock until the spawned worker owns the port (the spawn\n          // isn't \"done\" until then — same rule as the other gated\n          // launchers); the longer verification below runs unlocked.\n          await waitForHealth(port, getPlatformTimeout(15000));\n        } else if (restartFreed) {\n          spawnedScript = 'none (another launcher holds the spawn lock)';","sourceCodeStart":1159,"sourceCodeEnd":1195,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/worker-service.ts#L1159-L1195","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check `claude-mem status` — if the current owner is a healthy new-version worker, the restart effectively succeeded","If the old worker is the stuck owner, run `claude-mem stop` (or kill its pid) and then `claude-mem start`","Passive warning: if everything is healthy afterwards, no action needed"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async function describePortOwner(port: number): Promise<{ pid: number; healthy: boolean; version?: string }> {\n  const pid = await getCurrentWorkerPid(port, 2000);\n  let healthy = false, version: string | undefined;\n  try {\n    const res = await fetch(`http://127.0.0.1:${port}/health`);\n    healthy = res.ok;\n    version = (await res.json()).version;\n  } catch {}\n  return { pid: pid ?? -1, healthy, version };\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Before restarting, check the current port owner's health and version — a live successor needs no action","If the old worker is the stuck owner, stop/kill it explicitly instead of racing restarts","One restart initiator at a time prevents this fork in the first place"],"tags":["restart","port","worker","cli"],"backgroundTag":"port-in-use","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}