{"record":{"id":"e951faa5ec23c281","repo":"thedotmack/claude-mem","slug":"port-did-not-free-up-after-shutdown","errorCode":null,"errorMessage":"Port did not free up after shutdown","messagePattern":"Port did not free up after shutdown","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/worker-service.ts","lineNumber":1194,"sourceCode":"        exitWithStatus(\n          'error',\n          bootFailure ? `Failed to start worker: ${bootFailure}` : 'Failed to start worker'\n        );\n      } else {\n        exitWithStatus('ready', result === 'warming' ? 'Worker started; still warming up' : undefined);\n      }\n      break;\n    }\n\n    case 'stop': {\n      // Capture the dying worker's pid BEFORE shutdown so the PID-file\n      // cleanup below can prove it deletes THAT worker's file (or a dead\n      // pid's leftover) — never a live successor's (Phase 5).\n      const stoppedPid = await getCurrentWorkerPid(port, 2000);\n      await httpShutdown(port);\n      const freed = await waitForPortFree(port, getPlatformTimeout(15000));\n      if (!freed) {\n        logger.warn('SYSTEM', 'Port did not free up after shutdown', { port });\n      }\n      removePidFileIfOwner(stoppedPid);\n      logger.info('SYSTEM', 'Worker stopped successfully');\n      process.exit(0);\n      break;\n    }\n\n    case 'restart': {\n      logger.info('SYSTEM', 'Restarting worker');\n      // Capture the old worker's pid BEFORE shutdown so we can later prove\n      // the worker answering health checks is a NEW process, not the corpse.\n      const oldPid = await getCurrentWorkerPid(port, 2000);\n      // Track whether the worker accepted the shutdown POST: an accepted\n      // reason=restart shutdown means the dying worker spawns its OWN\n      // successor the moment its port frees (worker-shutdown.ts handoff).\n      // That handoff is the PRIMARY restart path; this CLI defers to it.\n      const shutdownAccepted = await httpShutdown(port, 'restart');\n","sourceCodeStart":1176,"sourceCodeEnd":1212,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/worker-service.ts#L1176-L1212","documentation":"During worker shutdown (stop flow), after httpShutdown the code waits up to the platform-adjusted 15s for the port to become free. If the port is still occupied it logs this warning but continues: it removes the PID file only if it belongs to the just-stopped PID and exits 0, leaving the port possibly still held.","triggerScenarios":"The stop command's waitForPortFree times out because the worker process (or a child) ignores SIGTERM and lingers, another process grabbed the port, or the socket sits in a lingering state.","commonSituations":"Unresponsive worker due to a hung request or stuck background thread; orphaned child process holding the listening socket; restarting too quickly so TIME_WAIT/old process still owns the port.","solutions":["Find and kill the lingering process: lsof -i :<port> then kill -9 the PID.","Wait a few seconds and retry the stop/start — the port usually frees once the process exits.","Use the platform timeout env/tuning if 15s is too short for slow shutdowns on your platform.","Check for orphaned child processes of the worker (chroma/uv) that keep the socket alive and terminate them."],"exampleFix":"// before: blind restart\nclaude-mem stop && claude-mem start\n// after: ensure the port is free first\nclaude-mem stop\nlsof -ti :37777 | xargs -r kill -9\nclaude-mem start","handlingStrategy":"retry","validationCode":"async function isPortFree(port) {\n  return new Promise(res => {\n    const s = require('net').createServer();\n    s.once('error', () => res(false));\n    s.once('listening', () => s.close(() => res(true)));\n    s.listen(port);\n  });\n}","typeGuard":null,"tryCatchPattern":"const freed = await waitForPortFree(port, 15000);\nif (!freed) {\n  const pids = execSync(`lsof -ti :${port}`).toString().trim();\n  if (pids) process.kill(Number(pids.split('\\n')[0]), 'SIGKILL');\n}","preventionTips":["Ensure the worker handles SIGTERM promptly and exits its background threads","Watch for orphaned children (chroma/uv) after shutdown","Insert a small delay between stop and start when scripting restarts"],"tags":["shutdown","port","process-lifecycle"],"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"}